Какие данные в Go попадают в кучу (heap), а не в стек (stack)?

Ответ

В Go решение о том, где размещать переменную — в стеке (stack) или в куче (heap) — принимает компилятор на основе анализа побега (escape analysis).

По умолчанию переменные размещаются в стеке, так как это очень быстро (выделение и освобождение памяти — это просто сдвиг указателя стека). Однако, если компилятор определяет, что на переменную будут ссылаться после завершения функции, в которой она была создана, она "сбегает" в кучу. Память в куче управляется сборщиком мусора (GC).

Основные причины "побега" в кучу:


  1. Возврат указателя на локальную переменную: Если функция возвращает указатель на переменную, созданную внутри неё, эта переменная должна пережить вызов функции, поэтому она размещается в куче.



  2. Размер данных неизвестен на этапе компиляции: Например, при создании среза make([]int, n), где n — это переменная, а не константа. Компилятор не может заранее определить размер на стеке.



  3. Переменная используется в другой горутине или в замыкании, которое переживает функцию: Так как горутины выполняются независимо, нет гарантии, что стек создавшей их функции будет существовать в момент доступа к переменной.



  4. Сохранение указателя на переменную в срезе, который сам может "сбежать".


Пример:

// user "сбегает" в кучу, так как на него возвращается указатель
func createUser(name string) *User {
    u := User{Name: name}
    return &u
}

// x НЕ сбегает в кучу, так как используется только внутри функции
func processValue() {
    x := 42
    fmt.Println(x)
}

Как проверить?
Узнать, какие переменные сбегают в кучу, можно с помощью флага компилятора -gcflags="-m":

# Команда покажет результаты анализа побега
go build -gcflags="-m" ./...