Память в Go: Stack и Heap
Краткое описание
Разбираем, как Go работает с памятью: деление на стек и кучу, где хранятся переменные, как выделяется память для функций и как она освобождается.
Структура памяти
Выделение памяти приложению
При запуске программа запрашивает у операционной системы память. ОС выделяет блок памяти, который делится на две части:
- Stack (стек) — для простых типов и локальных переменных
- Heap (куча) — для сложных типов и динамических данных
┌─────────────────────────┐
│ Оперативная память │
├─────────────────────────┤
│ │
│ Stack (стек) │
│ ┌──────────┐ │
│ │ main() │ │
│ │ foo() │ │
│ └──────────┘ │
│ │
├─────────────────────────┤
│ │
│ Heap (куча) │
│ (больше размером) │
│ │
└─────────────────────────┘
Где хранятся переменные
Простые типы → Stack
Примитивные типы (int, bool, float64) хранятся на стеке.
var volume int = 123 // Хранится на стеке (64 бита в двоичном виде)
Стек:
┌──────────────┐
│ 123 (64 бит) │ ← volume
└──────────────┘
Сложные типы → Stack + Heap
Сложные типы (строки, слайсы, структуры) частично на стеке, частично на куче:
- На стеке — ссылка/указатель
- На куче — фактические данные
Stack Heap
┌──────┐ ┌──────────────┐
│ ptr │──────→│ данные │
└──────┘ └──────────────┘
Стек для каждой функции
Важно: каждая функция имеет свой локальный стек (фрейм стека).
func main() {
var volume int = 123 // На стеке main
foo()
}
func foo() {
var value int = 987 // На стеке foo
}
Визуализация:
Stack
┌────────────────┐
│ foo() │
│ value = 987 │ ← Выделяется при вызове foo()
├────────────────┤
│ main() │
│ volume = 123 │ ← Выделяется при запуске main()
└────────────────┘
Жизненный цикл
1. Запуск программы
ОС выделяет память → делится на Stack и Heap
2. Выполнение main()
func main() {
var volume int = 123 // Выделяется на стеке main
foo()
}
Stack:
┌─────────────┐
│ main() │
│ volume=123 │
└─────────────┘
3. Вызов foo()
func foo() {
var value int = 987 // Выделяется на стеке foo
}
Stack:
┌─────────────┐
│ foo() │ ← Новый фрейм
│ value=987 │
├─────────────┤
│ main() │
│ volume=123 │
└─────────────┘
4. Завершение foo()
Память foo освобождается, стек возвращается к main.
Stack:
┌─────────────┐
│ main() │
│ volume=123 │
└─────────────┘
5. Завершение программы
Вся память возвращается ОС.
Ключевые моменты
1. Два типа памяти
- Stack: быстрый, автоматическое управление, для простых типов
- Heap: медленнее, требует сборки мусора, для сложных типов
2. Простые типы на стеке
int, int8, int16, int32, int64, uint, float32, float64, bool → Stack
3. Каждая функция имеет свой стек
При вызове функции выделяется фрейм стека, при завершении — освобождается.
4. Автоматическое управление
Go автоматически выделяет и освобождает память на стеке.
5. Куча для сложных данных
Строки, слайсы, карты, структуры → часть на стеке (ссылка), часть на куче (данные).
6. Сборщик мусора (GC)
За очистку кучи отвечает Garbage Collector (автоматически).
Что запомнить
- Память делится на Stack и Heap
- Простые типы (
int,bool) → Stack - Каждая функция → свой фрейм стека
- При завершении функции → память освобождается
- Куча нужна для сложных типов
- Go автоматически управляет памятью
- Stack быстрее, Heap больше
Визуальная схема работы
1. Запуск программы
┌──────┐
│ ОС │ выделяет память
└──────┘
2. Создание main()
Stack: [main: volume=123]
3. Вызов foo()
Stack: [foo: value=987]
[main: volume=123]
4. Завершение foo()
Stack: [main: volume=123]
5. Завершение программы
Stack: []
Память возвращается ОС
Итог: Go автоматически управляет памятью. Простые переменные на стеке, сложные — частично на куче. При завершении функции её стек освобождается. Всё просто и автоматически!