Ответ
В Go, в отличие от C++, решение о размещении переменной в стеке (stack) или в куче (heap) принимает компилятор, а не программист с помощью ключевых слов. Этот процесс называется анализом побега (escape analysis).
Основное правило:
- Стек (Stack): Память выделяется для переменных, время жизни которых ограничено вызовом одной функции. Это очень быстро, так как память освобождается автоматически при выходе из функции.
- Куча (Heap): Память выделяется для переменных, которые должны "пережить" функцию, в которой были созданы. Такие переменные "сбегают" (escape) в кучу. Память в куче управляется сборщиком мусора (GC).
Ключевой механизм: Escape Analysis
Компилятор анализирует код, чтобы определить, может ли ссылка на переменную быть использована за пределами её исходной области видимости. Переменная "сбегает" в кучу, если:
- Возвращается указатель на переменную: Если функция возвращает указатель на свою локальную переменную, эта переменная должна быть размещена в куче, иначе указатель будет указывать на мусор.
func getIntPtr() *int { i := 42 return &i // "i" сбегает в кучу }
- Переменная слишком большая: Массивы или структуры очень большого размера могут быть принудительно размещены в куче, даже если они не "сбегают", чтобы не вызвать переполнение стека.
- Захват переменной в замыкании (closure): Если горутина или замыкание захватывает переменную из внешней функции, и время жизни горутины/замыкания превышает время жизни функции, переменная сбегает в кучу.
func main() { x := 10 go func() { fmt.Println(x) // "x" сбегает в кучу, т.к. используется в другой горутине }() }
- Отправка указателя в канал: Это частный случай, когда переменная становится доступной в другой части программы (другой горутине).
Пример: Стек vs Куча
// user останется в стеке, т.к. используется только внутри функции
func stackAlloc() {
user := User{Name: "Alice", Age: 30}
// ... работа с user
}
// user "сбежит" в кучу, т.к. на него возвращается указатель
func heapAlloc() *User {
user := User{Name: "Bob", Age: 25}
return &user
}
Как проверить?
Вы можете попросить компилятор сообщить о результатах escape-анализа с помощью флага -m
:
go build -gcflags="-m" .