Ваш массив размещен в куче, а целые числа не упакованы.
Источник вашей путаницы, вероятно, связан с тем, что люди говорили, что ссылочные типы размещаются в куче, а типы значений - в стеке. Это не совсем точное представление.
Все локальные переменные и параметры размещаются в стеке. Это включает в себя как типы значений, так и ссылочные типы. Разница между ними состоит только в том, что хранится в переменной. Неудивительно, что для типа значения значение типа сохраняется непосредственно в переменной, а для ссылочного типа значение типа сохраняется в куче, а ссылка к этому значению относится то, что хранится в переменной.
То же самое относится и к полям. Когда память выделяется для экземпляра агрегатного типа (класса или структуры), она должна включать хранилище для каждого из своих полей экземпляра. Для полей ссылочного типа это хранилище содержит только ссылку на значение, которое само будет выделено в куче позже. Для полей типа значения это хранилище содержит фактическое значение.
Итак, даны следующие типы:
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 ┌───────────────────┐
│ I │
4 ├───────────────────┤
│ S │
8 ├───────────────────┤
│ L │
│ │
16 └───────────────────┘
Теперь, если у вас есть три локальные переменные в функции, типов RefType
, ValType
и int[]
, например:
RefType refType;
ValType valType;
int[] intArray;
тогда ваш стек может выглядеть так:
0 ┌───────────────────┐
│ refType │
4 ├───────────────────┤
│ valType │
│ │
│ │
│ │
20 ├───────────────────┤
│ 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 │ -- heap address of `refType`
4 ├───────────────────┤
│ 200 │ -- value of `valType.I`
│ 0x4A984C10 │ -- heap address of `valType.S`
│ 0x44556677 │ -- low 32-bits of `valType.L`
│ 0x00112233 │ -- high 32-bits of `valType.L`
20 ├───────────────────┤
│ 0x4AA4C288 │ -- heap address of `intArray`
24 └───────────────────┘
Память по адресу 0x4A963B68 (значение refType
) будет выглядеть примерно так:
0 ┌───────────────────┐
│ 100 │ -- value of `refType.I`
4 ├───────────────────┤
│ 0x4A984D88 │ -- heap address of `refType.S`
8 ├───────────────────┤
│ 0x89ABCDEF │ -- low 32-bits of `refType.L`
│ 0x01234567 │ -- high 32-bits of `refType.L`
16 └───────────────────┘
Память по адресу 0x4AA4C288 (значение intArray
) будет выглядеть примерно так:
0 ┌───────────────────┐
│ 4 │ -- length of array
4 ├───────────────────┤
│ 300 │ -- `intArray[0]`
8 ├───────────────────┤
│ 301 │ -- `intArray[1]`
12 ├───────────────────┤
│ 302 │ -- `intArray[2]`
16 ├───────────────────┤
│ 303 │ -- `intArray[3]`
20 └───────────────────┘
Теперь, если вы передали intArray
другой функции, значение, помещаемое в стек, будет 0x4AA4C288, адрес массива, не копия массива.