Как .NET хранит объекты в памяти
Причиной многих заблуждений относительно того как .NET хранит объекты в памяти, является ошибочное представление, что значимые типы всегда хранятся в стеке (stack), а ссылочные в куче (heap). На самом деле ссылочные типы - всегда хранятся в куче, а вот значимые типы могут храниться как в куче, так и в стеке.
Все локальные переменные (local variables) и параметры метода хранятся в стеке. Это касается переменных и параметров и значимых (value types) и ссылочных типов (reference types). Разница между ними в том, что они хранят. Переменные и параметры значимых типов - хранят само значение переменной. А переменные и параметры ссылочных типов - хранят ссылку на объект расположенный в куче.
Теперь рассмотрим поля (fields).
Поля являются частью сложных типов, таких как классы (class) и структуры (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 байт
Количество памяти занимаемой объектами значимых типов, можно получить с помощью ключевого слова sizeof. Подробнее на MSDN.
Так что память выделенная под объекты обоих типов выглядит так:
0 ┌───────────────────┐
│ I │
4 ├───────────────────┤
│ S │
8 ├───────────────────┤
│ L │
│ │
16 └───────────────────┘
Теперь представим, что наша функция содержит три локальных переменных:
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 └───────────────────┘
Таким образом при поведении по-умолчанию:
- Место под локальные переменные и параметры метода всегда выделяется в стеке
- Локальные переменные и параметры значимого типа - всегда содержат значение переменной
- Локальные переменные и параметры ссылочного типа - всегда содержат ссылку на место в куче
- Место под поля может выделяться как в стеке, так и в куче в зависимости от того где выделяется память под объект, который содержит это поле.
- Поле значимого типа - всегда содержит значение
- Поле ссылочного типа - всегда содержит ссылку на адрес в куче
- Место под массив всегда выделяется в куче