Материал проходит вычитку — возможны неточности.
Урок 17 из 118

Почему значение сбегает

Подозрение пало на сбежавшие значения: Миша был уверен, что дело в его новом коде, но не понимал, какие значения и почему. Сергей предложил разобрать причины одну за другой — и Миша узнал в каждой кусок собственного сервиса.

Главная мысль

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

Возврат указателя

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 КБ отправляются в кучу.
Задание · вопросы

Проверьте себя

Вопрос 1 из 3

Сколько причин сбегания значения в кучу перечислено в уроке?

Вопрос 2 из 3

Чем бокс значения отличается от бокса указателя в интерфейс?

Вопрос 3 из 3

Отправка значения в канал — что именно сбегает?

Итоги

Семь причин сбегания: указатель наружу, замыкание, сбежавшая структура, бокс в интерфейс, канал, слайс с неизвестной длиной, размер. Все — про «значение может пережить кадр функции», и в каждом случае решает escape analysis: сбегает копия или объект за указателем, а не обязательно исходная переменная.

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки