Почему значение сбегает
Подозрение пало на сбежавшие значения: Миша был уверен, что дело в его новом коде, но не понимал, какие значения и почему. Сергей предложил разобрать причины одну за другой — и Миша узнал в каждой кусок собственного сервиса.
значение сбегает в кучу по семи причинам: возврат указателя, захват замыканием, хранение в сбежавшей структуре, бокс в интерфейс, отправка в канал, слайс с неизвестной длиной и большой размер.
Возврат указателя
func newRecord(id int) *record {
r := &record{id: id}
return r // r сбегает в кучу
}
Функция возвращает *T — объект обязан пережить возврат. Если вызов не заинлайнен и указатель реально покидает кадр, объект уходит в кучу: в C это была бы висячая ссылка, в Go — аллокация в куче (вспомни). Но аллокация не обязательна: после инлайнинга без дальнейшего сбегания тот же объект остаётся на стеке — решает анализ, а не сам факт возврата указателя.
Захват замыканием
func counter() func() int {
n := 0
return func() int {
n++
return n // n сбегает в кучу
}
}
Замыкание живёт дольше создавшей его функции, захваченные переменные уезжают вместе с ним.
Ещё пять причин
- Хранение в сбежавшей структуре. Сбежавшая структура уходит в кучу целиком, вместе с полями.
- Бокс в интерфейс. Когда в интерфейс кладут значение конкретного типа, его копия уходит в кучу, только если сам интерфейс сбегает, — решает escape analysis, а не бокс. Для указателей исключение: слово данных интерфейса и есть указатель, бокс ничего не аллоцирует. Отдельная деталь: для целых от 0 до 255 runtime держит готовую таблицу, и бокс такого числа обходится без аллокации (оптимизация Go 1.15, рассчитывать на неё не стоит). Осторожно с проверкой:
fmt.Println(x)сам создаёт сбегание, поэтому «бокс всегда аллоцирует» по его выводу — иллюзия. - Отправка в канал. Значение из канала прочитают в другой горутине в неизвестный момент — в канал уходит копия, и сбегает именно она (или объект за переданным указателем), а не исходная переменная отправителя. В выводе анализа это выглядит как
leaking param. - Слайс с неизвестной длиной.
make([]T, n)с переменнымnразмещает массив в куче — размер кадра известен на этапе компиляции, аnстанет известен только в рантайме. До Go 1.25 это было без исключений; с Go 1.26 не-сбегающийmakeпеременной длины до ~32 байт может лечь на стек (оптимизация variablemake). Рост слайса — куча в текущей реализации, но это не гарантия спецификации. - Размер. Большой объект уезжает в кучу, даже если никуда не сбегает: стек ограничен. Порог меняется от версии к версии; в текущем тулчейне локальные переменные больше 128 КБ отправляются в кучу.
Проверьте себя
Сколько причин сбегания значения в кучу перечислено в уроке?
Чем бокс значения отличается от бокса указателя в интерфейс?
Отправка значения в канал — что именно сбегает?
правильных ответов
Семь причин сбегания: указатель наружу, замыкание, сбежавшая структура, бокс в интерфейс, канал, слайс с неизвестной длиной, размер. Все — про «значение может пережить кадр функции», и в каждом случае решает escape analysis: сбегает копия или объект за указателем, а не обязательно исходная переменная.
10 000 ₽ · 60 минут · онлайн