Ваш массив размещается в куче, а целые числа не упакованы.
Вероятно, источник вашей путаницы связан с тем, что люди говорили, что ссылочные типы размещаются в куче, а типы значений - в стеке. Это не совсем точное представление.
Все локальные переменные и параметры размещаются в стеке. Это включает как типы значений, так и ссылочные типы. Разница между ними заключается только в том, что хранится в переменной. Неудивительно, что для типа значения значение типа сохраняется непосредственно в переменной, а для ссылочного типа значение типа сохраняется в куче, а ссылка на это значение хранится в переменной.
То же самое относится к полям. Когда память выделяется для экземпляра агрегатного типа (a class
или a struct
), она должна включать хранилище для каждого из своих полей экземпляра. Для полей ссылочного типа это хранилище содержит только ссылку на значение, которое само будет выделено в куче позже. Для полей типа значения это хранилище содержит фактическое значение.
Итак, даны следующие виды:
class RefType{
public int I;
public string S;
public long L;
}
struct ValType{
public int I;
public string S;
public long L;
}
Значения каждого из этих типов потребовали бы 16 байтов памяти (при условии 32-битного размера слова). Поле I
в каждом случае занимает 4 байта для хранения своего значения, поле S
занимает 4 байта для хранения своей ссылки, а поле L
занимает 8 байтов для хранения своего значения. Так что память для значения обоих RefType
и ValType
выглядит так:
0 ┌───────────────────┐
│ я │
4 ├───────────────────┤
│ S │
8 ├───────────────────┤
│ L │
│ │
16 └───────────────────┘
Теперь , если у вас три локальные переменные в функции, типов RefType
, ValType
и int[]
, как это:
RefType refType;
ValType valType;
int[] intArray;
тогда ваш стек может выглядеть так:
0 ┌───────────────────┐
│ refType │
4 ├───────────────────┤
│ valType │
│ │
│ │
│ │
20 ├───────────────────┤
Ar intArray │
24 └───────────────────┘
Если вы присвоили значения этим локальным переменным, вот так:
refType = new RefType();
refType.I = 100;
refType.S = "refType.S";
refType.L = 0x0123456789ABCDEF;
valType = new ValType();
valType.I = 200;
valType.S = "valType.S";
valType.L = 0x0011223344556677;
intArray = new int[4];
intArray[0] = 300;
intArray[1] = 301;
intArray[2] = 302;
intArray[3] = 303;
Тогда ваш стек может выглядеть примерно так:
0 ┌───────────────────┐
│ 0x4A963B68 │ - адрес кучи `refType`
4 ├───────────────────┤
│ 200 │ - значение `valType.I`
│ 0x4A984C10 │ - адрес кучи `valType.S`
│ 0x44556677 │ - младшие 32 бита `valType.L`
│ 0x00112233 │ - старшие 32-битные из `valType.L`
20 ├───────────────────┤
│ 0x4AA4C288 │ - адрес кучи `intArray`
24 └───────────────────┘
Память по адресу 0x4A963B68
(значение refType
) будет что-то вроде:
0 ┌───────────────────┐
│ 100 │ - значение `refType.I`
4 ├───────────────────┤
│ 0x4A984D88 │ - адрес кучи `refType.S`
8 ├───────────────────┤
│ 0x89ABCDEF │ - младшие 32 бита `refType.L`
│ 0x01234567 │ - старшие 32 бита `refType.L`
16 └───────────────────┘
Память по адресу 0x4AA4C288
(значение intArray
) будет что-то вроде:
0 ┌───────────────────┐
│ 4 │ - длина массива
4 ├───────────────────┤
│ 300 │ - `intArray [0]`
8 ├───────────────────┤
│ 301 │ - `intArray [1]`
12 ├───────────────────┤
│ 302 │ - `intArray [2]`
16 ├───────────────────┤
│ 303 │ - `intArray [3]`
20 └───────────────────┘
Теперь, если вы передадите intArray
другой функции, значение, помещаемое в стек, будет 0x4AA4C288
адресом массива, а не его копией.
Да, массив будет расположен в куче.
Ints внутри массива не будет упакован. Тот факт, что тип значения существует в куче, не обязательно означает, что он будет упакован. Упаковка будет происходить только тогда, когда тип значения, такой как int, назначен для ссылки на тип объекта.
Например
Не бокс:
Вставки:
Вы также можете проверить сообщение Эрика на эту тему:
источник
Чтобы понять, что происходит, вот несколько фактов:
Таким образом, если у вас есть массив целых чисел, массив размещается в куче, а целые числа, которые он содержит, являются частью объекта массива в куче. Целые числа находятся внутри объекта массива в куче, а не как отдельные объекты, поэтому они не упакованы.
Если у вас есть массив строк, это действительно массив строковых ссылок. Поскольку ссылки являются типами значений, они будут частью объекта массива в куче. Если вы помещаете строковый объект в массив, вы фактически помещаете ссылку на строковый объект в массиве, и строка является отдельным объектом в куче.
источник
Я думаю, что в основе вашего вопроса лежит недоразумение о ссылочных типах и типах значений. Наверное, с этим боролся каждый разработчик .NET и Java.
Массив - это просто список значений. Если это массив ссылочного типа (скажем, a
string[]
), то массив представляет собой список ссылок на различныеstring
объекты в куче, поскольку ссылка - это значение ссылочного типа. Внутренне эти ссылки реализованы в виде указателей на адрес в памяти. Если вы хотите визуализировать это, такой массив будет выглядеть так в памяти (в куче):[ 00000000, 00000000, 00000000, F8AB56AA ]
Это массив,
string
который содержит 4 ссылки наstring
объекты в куче (числа здесь шестнадцатеричные). В настоящее время только последнийstring
фактически указывает на что-либо (память инициализируется всеми нулями при выделении), этот массив будет в основном результатом этого кода в C #:Вышеуказанный массив будет в 32-битной программе. В 64-битной программе ссылки будут в два раза больше (
F8AB56AA
будут00000000F8AB56AA
).Если у вас есть массив типов значений (скажем, an
int[]
), то массив представляет собой список целых чисел, поскольку значением типа значения является само значение (отсюда и имя). Визуализация такого массива будет такой:[ 00000000, 45FF32BB, 00000000, 00000000 ]
Это массив из 4 целых чисел, где только второму целому присваивается значение (1174352571, которое является десятичным представлением этого шестнадцатеричного числа), а остальные целые числа будут 0 (как я уже говорил, память инициализируется в ноль и 00000000 в шестнадцатеричном виде - это 0 в десятичном виде). Код, который создал этот массив:
Этот
int[]
массив также будет храниться в куче.В качестве другого примера, память
short[4]
массива будет выглядеть так:[ 0000, 0000, 0000, 0000 ]
В качестве значения используется
short
2-байтовое число.Где хранится тип значения, это просто деталь реализации, как очень хорошо объясняет здесь Эрик Липперт , не свойственный различиям между типом значения и ссылочным типом (что является различием в поведении).
Когда вы что - то передать в метод (будь то ссылочный тип или тип значения) , то копия из значения типа фактически передается методу. В случае ссылочного типа значение является ссылкой (представьте себе, что это указатель на фрагмент памяти, хотя это также является деталью реализации), а в случае типа значения значением является сама вещь.
Упаковка происходит только при преобразовании типа значения в ссылочный тип. Этот код коробки:
источник
Это иллюстрации, изображающие приведенный выше ответ @P Daddy
И я проиллюстрировал соответствующее содержание в своем стиле.
источник
Массив целых чисел размещается в куче, ни больше, ни меньше. myIntegers ссылается на начало раздела, где размещены целые числа. Эта ссылка находится в стеке.
Если у вас есть массив объектов ссылочного типа, таких как тип объекта, myObjects [], расположенный в стеке, будет ссылаться на группу значений, которые сами ссылаются на объекты.
Подводя итог, если вы передаете myIntegers некоторым функциям, вы только передаете ссылку на место, где размещается реальная группа целых чисел.
источник
В вашем примере кода нет бокса.
Типы значений могут жить в куче, как в массиве целых чисел. Массив размещается в куче и хранит целые числа, которые являются типами значений. Содержимое массива инициализируется по умолчанию (int), которое оказывается равным нулю.
Рассмотрим класс, который содержит тип значения:
Переменная h относится к экземпляру HasAnInt, который живет в куче. Так получилось, что он содержит тип значения. Это совершенно нормально, «я» просто живу в куче, как это содержится в классе. В этом примере также нет бокса.
источник
Всем было сказано достаточно, но если кто-то ищет четкий (но неофициальный) образец и документацию о куче, стеке, локальных и статических переменных, обратитесь к полной статье Джона Скита о памяти в .NET - что происходит где
Выдержка:
Каждая локальная переменная (т. Е. Объявленная в методе) хранится в стеке. Это включает переменные ссылочного типа - сама переменная находится в стеке, но помните, что значение переменной ссылочного типа является только ссылкой (или нулем), а не самим объектом. Параметры метода также считаются локальными переменными, но если они объявлены с модификатором ref, они не получают свой собственный слот, а делят его с переменной, используемой в вызывающем коде. Смотрите мою статью о передаче параметров для более подробной информации.
Переменные экземпляра для ссылочного типа всегда находятся в куче. Вот где сам объект «живет».
Переменные экземпляра для типа значения хранятся в том же контексте, что и переменная, которая объявляет тип значения. Слот памяти для экземпляра эффективно содержит слоты для каждого поля в экземпляре. Это означает (учитывая две предыдущие точки), что переменная структуры, объявленная в методе, всегда будет в стеке, тогда как переменная структуры, которая является полем экземпляра класса, будет в куче.
Каждая статическая переменная хранится в куче, независимо от того, объявлена ли она в ссылочном типе или типе значения. Всего есть только один слот, независимо от того, сколько экземпляров создано. (Однако для того, чтобы существовал один слот, не нужно создавать никаких экземпляров.) Детали того, в какой именно куче находятся переменные, сложны, но подробно объясняются в статье MSDN на эту тему.
источник