Ваш масив розміщений на купі, а вкладені елементи не позначені коробкою.
Джерело вашої плутанини, ймовірно, тому, що люди сказали, що типи посилань виділяються на купі, а типи значень виділяються на стеку. Це не зовсім точне уявлення.
Всі локальні змінні та параметри виділяються на стеку. Сюди входять і типи значень, і типи посилань. Різниця між ними полягає лише в тому, що зберігається у змінній. Не дивно, що для типу значень значення типу зберігається безпосередньо у змінній, а для еталонного типу значення типу зберігається у купі, а посилання на це значення - це те, що зберігається у змінній.
Те саме стосується полів. Коли пам'ять виділяється для екземпляра сукупного типу (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 ├────────────────────┤
│ 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
адреса масиву, а не копія масиву.