Как определяется, куда положить переменную в памяти, и кто это определяет
Что проверяют
Знаете ли вы про escape analysis и понимаете ли, что выбор между стеком и кучей делает компилятор, а не ключевое слово new. Следом обычно просят показать, как это проверить, и назвать типичные причины, по которым переменная убегает в кучу.
Как отвечать
Решает компилятор при сборке. Escape analysis пытается доказать, что значение не переживёт вызов функции, и тогда кладёт его в кадр стека горутины. Если доказать не вышло, значение уходит в кучу. Явно попросить стек в Go нельзя.
Типичные причины попасть в кучу:
- указатель наружу: функция возвращает
&xили сохраняет его в глобальную переменную, поле долгоживущей структуры, канал; - интерфейс: передача значения в параметр типа
any, в том числе вfmt.Println, часто заставляет его убежать; - замыкание: переменная захвачена функцией, которая живёт дольше текущего вызова, например горутиной;
- размер:
make([]T, n)сn, неизвестным при компиляции, или слишком большой объект.
Проверяется это через go build -gcflags=-m: компилятор пишет moved to heap: x или escapes to heap, а с -m -m объясняет причину. Влияет и инлайнинг: после встраивания функции возвращаемый указатель может уже никуда не убегать.
Дальше работает рантайм. Кучу обслуживает аллокатор с классами размеров и локальными кэшами на каждый P, освобождает её сборщик мусора. Стек горутины не фиксирован: он начинается с нескольких килобайт и растёт копированием, лимит по умолчанию на 64-битных системах 1 ГБ.
Зачем это знать: аллокация в куче дороже и добавляет работы GC. Оптимизации в горячем коде подтверждают бенчмарком с -benchmem и профилем аллокаций в pprof.
Где спотыкаются
Говорят, что new и make всегда выделяют в куче, а локальные переменные всегда живут на стеке. Неверно и то и другое: неубегающий new(T) окажется на стеке, а локальная переменная, на которую вернули указатель, в куче.
Пугают переполнением стека, как в C. Стек горутины растёт сам, упасть можно разве что на бесконечной рекурсии, упёршись в лимит.
Начинают везде убирать указатели ради стека. Копирование большой структуры по значению бывает дороже аллокации, и без замера такая оптимизация вредит.