Стек против выделения кучи структур в Go и как они относятся к сборке мусора


Я новичок, и я испытываю некоторый конгитивный диссонанс между программированием на основе стека в стиле C, где автоматические переменные живут в стеке, а выделенная память живет в куче и и программирование на основе стека в стиле Python, где единственное, что живет в стеке,-это ссылки/указатели на объекты в куче.

насколько я могу судить, две следующие функции дают один и тот же результат:

func myFunction() (*MyStructType, error) {
    var chunk *MyStructType = new(HeaderChunk)

    ...

    return chunk, nil
}


func myFunction() (*MyStructType, error) {
    var chunk MyStructType

    ...

    return &chunk, nil
}

т. е. выделить новую структуру и вернуть оно.

если бы я написал это в C, первый из них поместил бы объект в кучу, а второй поместил бы его в стек. Первый вернет указатель на кучу, второй вернет указатель на стек, который испарился бы к тому времени, когда функция вернулась, что было бы плохо.

если бы я написал его на Python (или на многих других современных языках, кроме C#), Пример 2 был бы невозможен.

Я понимаю, что идти мусор собирает оба значения, поэтому обе вышеуказанные формы в порядке.

цитата:

обратите внимание, что, в отличие от C, это совершенно нормально, чтобы вернуть адрес a локальная переменная; память, связанная с переменной, сохраняется после того, как функция возвращает. Фактически, принимая адрес композита литерал выделяет новый экземпляр каждый раз, когда он оценивается, поэтому мы можно объединить эти две последние строки.

http://golang.org/doc/effective_go.html#functions

но это вызывает пару вопросов.

1-в Примере 1 структура объявляется в куче. Насчет примера 2? Это объявлено в стеке таким же образом, как и в C, или оно тоже идет в кучу?

2-Если Пример 2 объявлен в стеке, как он остается доступным после возврата функции?

3-Если фактически объявлен Пример 2 в куче, как получилось, что структуры передаются по значению а не по ссылке? В чем смысл указателей в данном случае?

4 116

4 ответа:

стоит отметить, что слова "стек" и "куча" не встречаются в спецификации языка. Ваш вопрос сформулирован с помощью "... объявлен в стеке "и"...объявлено в куче", но обратите внимание, что синтаксис объявления Go ничего не говорит о стеке или куче.

что технически делает ответ на все ваши вопросы реализации зависит. На самом деле, конечно, есть стек (за goroutine!) и куча и некоторые вещи идут в стек и кучу. В в некоторых случаях компилятор следует жестким правилам (например,"new всегда выделяет в куче"), а в других компилятор выполняет "escape-анализ", чтобы решить, может ли объект жить в стеке или он должен быть выделен в куче.

в вашем примере 2 escape-анализ покажет указатель на экранирование структуры, и поэтому компилятор должен будет выделить структуру. Я думаю, что текущая реализация Go следует жесткому правилу в этом случае, однако, что если адрес взятая из любой части структуры, структура переходит в кучу.

по вопросу 3 мы рискуем запутаться в терминологии. Все в Go передается по значению, нет передачи по ссылке. Здесь вы возвращаете значение указателя. В чем смысл указателей? Рассмотрим следующую модификацию вашего примера:

type MyStructType struct{}

func myFunction1() (*MyStructType, error) {
    var chunk *MyStructType = new(MyStructType)
    // ...
    return chunk, nil
}

func myFunction2() (MyStructType, error) {
    var chunk MyStructType
    // ...
    return chunk, nil
}

type bigStruct struct {
    lots [1e6]float64
}

func myFunction3() (bigStruct, error) {
    var chunk bigStruct
    // ...
    return chunk, nil
}

Я изменил myFunction2, чтобы вернуть структуру, а не адрес структуры. Сравните выходные данные сборки myFunction1 и myFunction2 теперь,

--- prog list "myFunction1" ---
0000 (s.go:5) TEXT    myFunction1+0(SB),-24
0001 (s.go:6) MOVQ    $type."".MyStructType+0(SB),(SP)
0002 (s.go:6) CALL    ,runtime.new+0(SB)
0003 (s.go:6) MOVQ    8(SP),AX
0004 (s.go:8) MOVQ    AX,.noname+0(FP)
0005 (s.go:8) MOVQ    ,.noname+8(FP)
0006 (s.go:8) MOVQ    ,.noname+16(FP)
0007 (s.go:8) RET     ,

--- prog list "myFunction2" ---
0008 (s.go:11) TEXT    myFunction2+0(SB),-16
0009 (s.go:12) LEAQ    chunk+0(SP),DI
0010 (s.go:12) MOVQ    ,AX
0011 (s.go:14) LEAQ    .noname+0(FP),BX
0012 (s.go:14) LEAQ    chunk+0(SP),BX
0013 (s.go:14) MOVQ    ,.noname+0(FP)
0014 (s.go:14) MOVQ    ,.noname+8(FP)
0015 (s.go:14) RET     ,

Не волнуйтесь, что вывод myFunction1 здесь отличается от ответа петерсо (отлично). Мы, очевидно, запускаем разные компиляторы. В противном случае см., Что я модифицировал myFunction2 для возврата myStructType, а не *myStructType. Вызов среды выполнения.новых нет, что в некоторых случаях было бы хорошо. Подождите, хотя, вот myFunction3,

--- prog list "myFunction3" ---
0016 (s.go:21) TEXT    myFunction3+0(SB),00000-8000016
0017 (s.go:22) LEAQ    chunk+-8000000(SP),DI
0018 (s.go:22) MOVQ    ,AX
0019 (s.go:22) MOVQ    00000,CX
0020 (s.go:22) REP     ,
0021 (s.go:22) STOSQ   ,
0022 (s.go:24) LEAQ    chunk+-8000000(SP),SI
0023 (s.go:24) LEAQ    .noname+0(FP),DI
0024 (s.go:24) MOVQ    00000,CX
0025 (s.go:24) REP     ,
0026 (s.go:24) MOVSQ   ,
0027 (s.go:24) MOVQ    ,.noname+8000000(FP)
0028 (s.go:24) MOVQ    ,.noname+8000008(FP)
0029 (s.go:24) RET     ,

по-прежнему нет вызова во время выполнения.новый, и да, это действительно работает, чтобы вернуть объект 8 МБ значение. Это работает, но вы обычно не хотите. Точка указателя здесь будет заключаться в том, чтобы не нажимать на объекты размером 8 МБ.

type MyStructType struct{}

func myFunction1() (*MyStructType, error) {
    var chunk *MyStructType = new(MyStructType)
    // ...
    return chunk, nil
}

func myFunction2() (*MyStructType, error) {
    var chunk MyStructType
    // ...
    return &chunk, nil
}

в обоих случаях текущие реализации Go будут выделять память для struct типа MyStructType на куче и возвращает его адрес. Функции эквивалентны; источник ASM компилятора тот же.

--- prog list "myFunction1" ---
0000 (temp.go:9) TEXT    myFunction1+0(SB),-12
0001 (temp.go:10) MOVL    $type."".MyStructType+0(SB),(SP)
0002 (temp.go:10) CALL    ,runtime.new+0(SB)
0003 (temp.go:10) MOVL    4(SP),BX
0004 (temp.go:12) MOVL    BX,.noname+0(FP)
0005 (temp.go:12) MOVL    ,AX
0006 (temp.go:12) LEAL    .noname+4(FP),DI
0007 (temp.go:12) STOSL   ,
0008 (temp.go:12) STOSL   ,
0009 (temp.go:12) RET     ,

--- prog list "myFunction2" ---
0010 (temp.go:15) TEXT    myFunction2+0(SB),-12
0011 (temp.go:16) MOVL    $type."".MyStructType+0(SB),(SP)
0012 (temp.go:16) CALL    ,runtime.new+0(SB)
0013 (temp.go:16) MOVL    4(SP),BX
0014 (temp.go:18) MOVL    BX,.noname+0(FP)
0015 (temp.go:18) MOVL    ,AX
0016 (temp.go:18) LEAL    .noname+4(FP),DI
0017 (temp.go:18) STOSL   ,
0018 (temp.go:18) STOSL   ,
0019 (temp.go:18) RET     ,

звонки

в вызове функции значение функции и аргументы вычисляются в обычный порядок. После того, как они будут оценены, параметры вызова передаются по значению в функцию и называется функция начинается исполнение. Возвращаемые параметры функции передаются по значению вернуться к вызывающей функции, когда функция возвращается.

все функции и возвращаемые параметры передаются по значению. Возвращаемое значение параметра с типом *MyStructType - это адрес.

по данным заходим и FAQ:

если компилятор не может доказать, что переменная не ссылается после функция возвращает, затем компилятор должен выделить переменную on куча собранного мусора, чтобы избежать ошибок висящего указателя.

вы не всегда знаете, если ваша переменная в стеке или куче.
...
Если вам нужно знать, где расположены ваши переменные, передайте флаг"- m "gc на" go build " или "go run" (например,go run -gcflags -m app.go).

Источник: http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#stack_heap_vars