2012-06-02 4 views
96

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

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

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

    ... 

    return chunk, nil 
} 


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

    ... 

    return &chunk, nil 
} 

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

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

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

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

Цитирую:

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

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

Но возникает несколько вопросов.

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

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

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

ответ

111

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

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

В вашем примере 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),$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 в. Очевидно, у нас работают разные компиляторы. В противном случае, см., Что я modfied 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, и да, это действительно работает, чтобы вернуть объект 8MB по значению. Он работает, но вы, как правило, этого не хотели. Точка указателя здесь заключалась бы в том, чтобы избежать нажатия вокруг объектов размером 8 МБ.

+7

Отличное спасибо. Я действительно не спрашивал: «В чем смысл указателей вообще», это было больше похоже на «то, что точка указателей, когда значения, похоже, ведут себя как указатели», и этот случай в любом случае оказывается спорным. – Joe

+7

Было бы полезно получить краткое объяснение сборки. – ElefEnt

39
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),$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

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

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

+0

Большое спасибо! Приобретенный, но я принимаю Соню из-за битва об анализе побега. – Joe

+0

peterSo, как вы и @Sonia производят эту сборку? У вас обоих одинаковое форматирование. Я не могу произвести его независимо от команды/флагов, попробовав objdump, go tool, otool. –

+0

А, получилось - gcflags. –

10

По Go's FAQ:

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

 Смежные вопросы

  • Нет связанных вопросов^_^