Ответ
В Go, как и во многих других языках, память для переменных выделяется либо в стеке, либо в куче. Выбор места размещения определяет компилятор на основе анализа побега (escape analysis).
Стек (Stack)
- Что хранится: Локальные переменные, аргументы функций, адреса возврата. В общем, данные, чей размер известен на этапе компиляции и чьё время жизни ограничено вызовом одной функции.
- Особенности:
- Скорость: Очень быстрое выделение и освобождение памяти (LIFO - Last-In, First-Out).
- Управление: Память управляется автоматически и очищается при выходе из функции. GC (сборщик мусора) не задействован.
- Динамический размер: В отличие от C/C++, стеки горутин в Go начинаются с небольшого размера (около 2KB) и могут динамически расти и сжиматься по мере необходимости.
Куча (Heap)
- Что хранится: Данные, которые "переживают" вызов функции (например, если на них возвращается указатель), или данные, чей размер неизвестен на этапе компиляции. Сюда попадают базовые массивы для слайсов и мап, данные в каналах и т.д.
- Особенности:
- Скорость: Выделение памяти медленнее, чем в стеке.
- Управление: Память управляется сборщиком мусора (GC). Он отслеживает, используются ли объекты, и освобождает память, когда на них не остается ссылок.
Анализ побега (Escape Analysis)
Это процесс, в ходе которого компилятор Go определяет, может ли переменная быть безопасно размещена в стеке или она должна "сбежать" (escape) в кучу.
func getIntPointer() *int {
x := 42
// x "сбегает" в кучу, потому что ссылка на неё
// будет существовать после завершения функции.
return &x
}
func main() {
// y будет размещена в стеке, т.к. используется
// только внутри main.
y := 100
fmt.Println(y)
ptr := getIntPointer() // ptr указывает на память в куче
fmt.Println(*ptr)
}
Вы можете увидеть результаты анализа побега, используя флаг -gcflags="-m"
при сборке: go build -gcflags="-m" .