📦 Память в C#: Стек и Куча
🧠 Как работает память в .NET?
Программы на C# (и .NET в целом) выполняются внутри CLR (Common Language Runtime). CLR управляет:
- Размещением переменных в памяти
- Жизненным циклом объектов
- Автоматическим распределением и освобождением памяти
Память делится на две ключевые области:
- Стек (Stack) — для временных и "лёгких" данных
- Куча (Heap) — для объектов и всего, что создаётся динамически
📐 Стек
📌 Что это?
Stack
— это область памяти, которая работает по принципу LIFO (Last In — First Out).
📍 В стеке хранятся:
- Локальные переменные примитивных типов (
int
,double
,bool
) - Параметры методов
- Ссылки на объекты в куче
- Значимые типы (структуры)
📊 Как работает стек?
Каждый вызов метода добавляет ("вталкивает") в стек фрейм, в котором хранятся его локальные переменные и параметры. Когда метод завершает выполнение — фрейм удаляется.
Main()
┌──────────────┐
│ int a = 10; │ ← локальная переменная
│ Call Foo() │ ← вызов метода
└──────────────┘
↓
Foo()
┌──────────────┐
│ int b = 5; │ ← переменная Foo
└──────────────┘
````
Когда `Foo` завершится, его фрейм удалится.
---
## 🧱 Куча
### 📌 Что это?
`Heap` — это область памяти для **долгоживущих объектов**, выделяемая динамически с помощью ключевого слова `new`.
### 📍 В куче хранятся:
* Объекты классов (`new Person()`)
* Массивы (`new int[1000]`)
* `string`
* Делегаты
* Всё, что создаётся через `new`
> Важно: в стеке остаётся **ссылка** на объект в куче.
---
## 💡 Пример: стек и куча вместе
```csharp
class Person {
public string Name;
}
void Main() {
int x = 42; // Значимый тип — в стеке
Person p = new Person(); // Ссылка p — в стеке, объект — в куче
p.Name = "Anton"; // Строка — в куче
}
📈 Визуализация
[ Stack ] [ Heap ]
───────────── ────────────────────────────
| x = 42 | | Person |
| p ─────── | ─────── ─────── ->| Name ───────┐ |
───────────── └─────────────┘ ↓
[ string: "Anton" ] <──────┘
🧩 Ключевые отличия
Характеристика | Стек (Stack) | Куча (Heap) |
---|---|---|
Управление | Автоматическое (при выходе из метода) | Управляется CLR (через GC) |
Скорость доступа | Очень быстрая | Медленнее |
Размер | Ограничен (обычно до 1 МБ) | Большой (выделяется ОС) |
Типы данных | Значимые (Value types), ссылки | Ссылочные (Reference types) |
Жизненный цикл | До конца метода | До исчезновения всех ссылок |
Пример | int , bool , struct |
class , string , array |
🔍 Ещё один пример с ASCII-схемой
struct Point {
public int X;
public int Y;
}
class Rectangle {
public Point TopLeft;
public Point BottomRight;
}
void Main() {
Rectangle r = new Rectangle();
r.TopLeft = new Point { X = 0, Y = 0 };
r.BottomRight = new Point { X = 10, Y = 10 };
}
[ Stack ]
───────────────
| r ─────────┐ |
───────────────
↓
[ Heap ]
────────────────────────────────────
| Rectangle |
| TopLeft: {X=0,Y=0} |
| BottomRight: {X=10,Y=10} |
────────────────────────────────────
🔎 Обрати внимание: хотя
Point
— структура (значимый тип), в куче они внутри объектаRectangle
.
🔄 Как взаимодействуют значимые и ссылочные типы
🧩 Значимые типы внутри ссылочных
Если значимый тип (например, struct
) находится внутри ссылочного типа (class
), то:
- Вся структура становится частью объекта, который хранится в куче
- Копирование объекта не копирует ссылку на структуру отдельно — она встроена
Пример:
struct Point {
public int X, Y;
}
class Figure {
public Point Center;
}
var fig = new Figure();
fig.Center.X = 5;
В памяти:
[ Stack ]
──────────────
| fig ──────┐ | ← ссылка на Figure
──────────────
↓
[ Heap ]
─────────────────────────────
| Figure |
| Center: { X=5, Y=0 } | ← структура внутри объекта
─────────────────────────────
Point
здесь не имеет своей "ссылки". Он физически встроен внутрь объектаFigure
.
🔁 Ссылочные типы внутри значимых
Если в структуре есть поле ссылочного типа (например, string
, class
), то:
- Сама структура хранится в стеке (или как часть другого объекта, если вложена)
- Но ссылка внутри неё указывает в кучу
Пример:
struct Message {
public string Text;
}
void Main() {
Message msg = new Message();
msg.Text = "Hello";
}
В памяти:
[ Stack ]
──────────────────
| msg.Text ────┐ | ← структура целиком в стеке
──────────────────
↓
[ Heap ]
──────────────
| "Hello" | ← строка — ссылочный тип
──────────────
🧠 Копирование: Важное различие
Point p1 = new Point { X = 1, Y = 2 };
Point p2 = p1; // Копируется ЗНАЧЕНИЕ
p2.X = 99;
Console.WriteLine(p1.X); // 1 — не изменилось!
Person a = new Person { Name = "Anton" };
Person b = a; // Копируется ССЫЛКА
b.Name = "John";
Console.WriteLine(a.Name); // John — объект один!
Тип | Поведение при присваивании |
---|---|
struct |
Копируется полностью |
class |
Копируется ссылка на один и тот же объект |
📌 Итого
Сценарий | Где хранятся данные |
---|---|
struct в class |
В куче, как часть объекта |
class в struct |
Сама структура в стеке, ссылка — в поле |
struct в стеке (локально) |
Целиком в стеке |
class в стеке (как ссылка) |
Только ссылка в стеке, объект — в куче |
Присваивание struct |
Копия |
Присваивание class |
Ссылка |
🔎 Это ключевое различие определяет поведение при передаче по значению и ссылке, а также влияет на производительность.
📝 Резюме
- Стек — быстрый, небольшой, используется для временных и примитивных данных
- Куча — гибкий, подходит для хранения объектов, управляется CLR
- Ссылки на объекты всегда хранятся в стеке, сами объекты — в куче
- Понимание различий важно для написания эффективного и предсказуемого по памяти кода
📌 Тема работы сборщика мусора (Garbage Collector) будет рассмотрена отдельно — она критически важна для работы с кучей, но требует детального внимания.