21 нояб. 2015 г., 20:54 отuserJoe

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

Я новичок в 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 был бы невозможен.

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

Цитировать

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

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

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

1 - В примере 1 структура объявлена в куче. Как насчет примера 2? Это объявляется в стеке так же, как это было бы в C, или это тоже происходит в куче?

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

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

Ответы на вопрос(4)

21 нояб. 2015 г., 13:50 отuser

You don't always know if your variable is allocated on the stack or heap.
...
If you need to know where your variables are allocated pass the "-m" gc flag to "go build" or "go run" (e.g., go run -gcflags -m app.go).

Source: http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#stack_heap_vars

03 июн. 2012 г., 01:13 отpeterSO
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
}

struct типаMyStructType на кучу и верни его адрес. Функции эквивалентны; источник asm компилятора такой же.

--- prog list "myFunction1" ---
0000 (temp.go:9) TEXT    myFunction1+0(SB),$8-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    $0,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),$8-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    $0,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     ,

Calls

In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution. The return parameters of the function are passed by value back to the calling function when the function returns.

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

29 сент. 2015 г., 20:07 отgchain

В соответствии сПерейти на часто задаваемые вопросы:

if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors.

03 июн. 2012 г., 02:01 отSonia

что слова «составлять» и "куча" не появляется нигде в спецификации языка. Ваш вопрос сформулирован как "... объявлен в стеке" & quot; и "... объявлено в куче" & quot; но обратите внимание, что синтаксис объявления Go ничего не говорит о стеке или куче.

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

В вашем примере 2 escape-анализ покажет указатель на 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),$16-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    $0,.noname+8(FP)
0006 (s.go:8) MOVQ    $0,.noname+16(FP)
0007 (s.go:8) RET     ,

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

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

--- prog list "myFunction3" ---
0016 (s.go:21) TEXT    myFunction3+0(SB),$8000000-8000016
0017 (s.go:22) LEAQ    chunk+-8000000(SP),DI
0018 (s.go:22) MOVQ    $0,AX
0019 (s.go:22) MOVQ    $1000000,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    $1000000,CX
0025 (s.go:24) REP     ,
0026 (s.go:24) MOVSQ   ,
0027 (s.go:24) MOVQ    $0,.noname+8000000(FP)
0028 (s.go:24) MOVQ    $0,.noname+8000008(FP)
0029 (s.go:24) RET     ,

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

ВАШ ОТВЕТ НА ВОПРОС