В каких случаях компилятор Go выделяет память в куче?

Ответ

Компилятор Go выделяет память в куче, когда переменная "убегает" (escapes) из своего первоначального скоупа. Это происходит в следующих основных случаях:

  1. Возврат указателя на локальную переменную. Если функция возвращает адрес своей локальной переменной, эта переменная должна продолжать существовать после завершения функции, поэтому она перемещается в кучу.

    func createInt() *int {
        x := 100 // "escapes to heap"
        return &x
    }
  2. Отправка указателя в горутину или канал. Компилятор не может отследить время жизни переменной, если она используется в другой горутине. Для безопасности он размещает её в куче.

    func main() {
        data := "some data" // "escapes to heap"
        go func() {
            fmt.Println(data) // Замыкание использует data
        }()
    }
  3. Размер переменной неизвестен на этапе компиляции. Данные для слайсов, мап и каналов всегда размещаются в куче, так как их размер может динамически меняться. Сама структура (заголовок слайса, указатель на мапу) может остаться на стеке.

    // Резервирование памяти под 1000 элементов происходит в куче
    s := make([]int, 1000)
  4. Переменная слишком большая для стека. Даже если переменная не "убегает", но имеет очень большой размер, компилятор может разместить её в куче, чтобы избежать переполнения стека.

  5. Сохранение значения в переменной типа interface{}. Когда значение присваивается переменной типа interface{}, происходит "упаковка" (boxing). Данные и информация о типе этого значения размещаются в куче.

    var i interface{}
    s := "hello world" // "s escapes to heap"
    i = s