Ответ
В Go память выделяется в стеке, когда компилятор с помощью анализа побега (escape analysis) может доказать, что объект не нужен за пределами текущей функции. Это означает, что время жизни объекта полностью ограничено функцией, в которой он был создан. Основные случаи, когда память вероятно будет выделена в стеке:
- Локальные переменные примитивных типов (int, float, bool, string и т.д.), если они не передаются по указателю и не возвращаются из функции.
func foo() { x := 42 // x выделится в стеке }
- Локальные переменные структур и массивов фиксированного размера, если они не содержат указателей, которые "убегают" из функции, и сами структуры/массивы не возвращаются из функции.
type Point struct { X, Y int } func bar() { p := Point{1, 2} // p выделится в стеке arr := [3]int{1, 2, 3} // arr выделится в стеке }
- Небольшие объекты, которые компилятор может оптимизировать и разместить на стеке, даже если они могли бы потенциально "убежать", но их использование позволяет это. (Это более продвинутый случай оптимизации компилятора).
Когда память вероятно будет выделена в куче (из-за "побега" объекта):
- Возврат указателя на локальную переменную из функции:
func createInt() *int { x := 10 return &x // &x "убегает", x будет в куче }
- Передача указателя на локальную переменную в другую горутину:
func spawnWorker() { data := "hello" go func() { // data "убегает" в другую горутину, будет в куче println(data) }() }
- Присвоение локальной переменной глобальной переменной или полю структуры, которая "убегает":
var globalVar *int func assignGlobal() { localVal := 5 globalVar = &localVal // &localVal "убегает", localVal будет в куче }
-
Использование интерфейсов: Если значение присваивается переменной интерфейсного типа, и это значение (или сама переменная интерфейса) "убегает" из функции, то базовое значение будет выделено в куче.
type Greeter interface { Greet() string } type Person struct { Name string } func (p Person) Greet() string { return "Hello, " + p.Name } func createGreeter() Greeter { p := Person{Name: "Alice"} return p // p присваивается интерфейсу и возвращается, p будет в куче }
- Создание срезов (slices) с динамическим размером или их расширение: Хотя сам заголовок среза (указатель, длина, емкость) может быть на стеке, базовый массив, на который он ссылается, обычно выделяется в куче, если его размер не известен на этапе компиляции или если он расширяется.
s := make([]int, 10) // Базовый массив s будет в куче
Важно помнить, что анализ побега — это оптимизация компилятора, и его поведение может меняться между версиями Go. Для точного определения можно использовать флаг компилятора -gcflags='-m'
.