Что хранится в стеке, а что в куче в Go? Каковы их особенности?

Ответ

В 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" .