Ваш массив размещается в куче, а целые числа не упакованы.
Вероятно, источник вашей путаницы связан с тем, что люди говорили, что ссылочные типы размещаются в куче, а типы значений - в стеке. Это не совсем точное представление.
Все локальные переменные и параметры размещаются в стеке. Это включает как типы значений, так и ссылочные типы. Разница между ними заключается только в том, что хранится в переменной. Неудивительно, что для типа значения значение типа сохраняется непосредственно в переменной, а для ссылочного типа значение типа сохраняется в куче, а ссылка на это значение хранится в переменной.
То же самое относится к полям. Когда память выделяется для экземпляра агрегатного типа (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
адресом массива, а не его копией.