Ответ
В Go, как и во многих других компилируемых языках, для выполнения программы используются две основные области памяти: стек (stack) и куча (heap).
Стек (Stack)
- Назначение: Хранение локальных переменных функций, аргументов и возвращаемых значений.
- Принцип работы: Работает по принципу LIFO (Last-In, First-Out). При вызове функции для неё создаётся новый фрейм (stack frame) на вершине стека. При выходе из функции её фрейм удаляется.
- Скорость: Очень быстрый, так как выделение и освобождение памяти — это простое смещение указателя стека.
- Размер: Размер стека для каждой горутины изначально небольшой (около 2KB), но может динамически расти при необходимости.
- Управление: Память управляется автоматически и не требует участия сборщика мусора (GC).
Куча (Heap)
- Назначение: Хранение данных, время жизни которых не связано с временем выполнения одной функции. Это могут быть глобальные переменные или данные, на которые есть указатели, "пережившие" функцию, в которой они были созданы.
- Принцип работы: Это большая область памяти, где блоки выделяются по запросу в произвольном порядке.
- Скорость: Выделение и освобождение памяти в куче — более медленная операция по сравнению со стеком.
- Размер: Ограничен только доступной оперативной памятью системы.
- Управление: Памятью в куче управляет сборщик мусора (GC), который периодически находит и освобождает неиспользуемые объекты.
Escape Analysis (Анализ "убегания")
Компилятор Go выполняет escape analysis, чтобы определить, где разместить переменную — в стеке или в куче. Переменная "убегает" в кучу, если компилятор не может доказать, что её использование безопасно ограничено рамками одной функции.
Примеры, когда переменная попадает в кучу:
- Возврат указателя на локальную переменную.
- Переменная используется в замыкании (closure), которое переживает функцию.
- Размер переменной неизвестен на этапе компиляции (например, срез, длина которого определяется в рантайме).
package main
func createInt() *int {
x := 42 // 'x' будет размещена в куче, так как указатель на неё возвращается из функции
return &x
}
func main() {
y := 10 // 'y' будет размещена в стеке функции main
ptr := createInt() // 'ptr' содержит адрес в куче
println(y, *ptr)
}