2017-01-28 30 views
3

Во многих языках, локальные переменные расположены в стеке вызововGO - Побег анализ

В JavaScript/Python, только закрытия переменные находятся в куче, потому что они должны жить за вызовы функций, они созданы.


В GO, некоторые GO типов (например, ломтик типа []int) делать ссылки на другие части памяти, как JavaScript/Python.

В GO не все типы переменных содержат ссылки, такие как Javascript/Python.

Например,

1) [3]int тип переменной b непосредственно хранит массив int-х, таких как C, за исключением того, что С позволяет получить доступ каждого местоположения элемента массива с использованием C синтаксис &b[index], для большего контроля

2) int переменная типа c непосредственно хранит значение int, например C, за исключением того, что C дает больше контроля, предоставляя синтаксис (&c), чтобы получить доступ к местоположению.


В GO, мое понимание, для локальных переменных, чтобы быть на куче/стек зависит от применения анализа побега компилятора в примере кода (ниже),

func foo() []int { 
    // the array lives beyond the call to foo in which it is created 
    var a [5]int 
    return a[:] // range operator 
} 

, который сообщает компилятору, что переменная a живет за ее пределами, поэтому выделяйте ее в кучу, но не стек.


Вопрос:

ли переменная a получить выделяется в куче?

+0

Обратите внимание, что не все языки ведут себя как C. На многих языках, таких как javascript и lisp, закрытие фактически является частью стека. Только то, что стек для языка не реализован в «стеке» в смысле С, а стек реализован как связанный список, а это означает, что стек на некоторых языках реализован в куче. Реализации Javascript могут либо реализовать классический функциональный стек связанных списков, либо какой-то механизм обнаружения закрытия для захвата закрытых свободных переменных. Оба ведут себя одинаково. – slebetman

+0

Вот одна хорошая серия статей о написании интерпретатора Ruby в Ruby, который объясняет традиционную реализацию закрытий на функциональных языках: http://hokstad.com/how-to-implement-closures. Обратите внимание на его объяснение «Вместо традиционного стека, поместите кадры активации (аргументы и локальные переменные) для вызовов функции/метода в куче, как связанный список» - он реализует стек для своего интерпретатора в куче – slebetman

ответ

7

В Go вы должны доверять компилятору, чтобы принять самое лучшее решение. Он по возможности выделяет память в стеке. См. Также the FAQ:

С точки зрения правильности, вам не нужно знать. Каждая переменная в Go существует до тех пор, пока есть ссылки на нее. Место хранения, выбранное реализацией, не имеет отношения к семантике языка.

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

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


Без оптимизации (встраивание), да a будет выделяться в куче. Мы можем проверить анализ побега пропусканием -gcflags='-m' (https://play.golang.org/p/l3cZFK5QHO):

$ nl -ba 1.go 
    1 package main 
    2 
    3 func inlined() []int { 
    4  var a [5]int 
    5  return a[:] 
    6 } 
    7 
    8 //go:noinline 
    9 func no_inline() []int { 
    10  var b [5]int 
    11  return b[:] 
    12 } 
    13 
    14 func main() { 
    15  var local_array [5]int 
    16  var local_var int 
    17  println(no_inline()) 
    18  println(inlined()) 
    19  println(local_array[:]) 
    20  println(&local_var) 
    21 } 
$ go build -gcflags='-m' 1.go 
# command-line-arguments 
./1.go:3: can inline inlined 
./1.go:18: inlining call to inlined 
./1.go:5: a escapes to heap 
./1.go:4: moved to heap: a 
./1.go:11: b escapes to heap 
./1.go:10: moved to heap: b 
./1.go:18: main a does not escape 
./1.go:19: main local_array does not escape 
./1.go:20: main &local_var does not escape 

Мы видим, что компилятор решил выделить inlined.a на линии 5 и no_inline.b по линии 10 в куче, потому что они оба побега их масштабов.

Однако после встраивания компилятор заметил, что a больше не исчезает, поэтому он определяет, что переменная может быть выделена в стеке снова (строка 18).

В результате переменная a распределяется по стеклу goroutine main, в то время как переменная b выделяется в куче. Как видно из вывода, адрес b находится на 0x1043xxxx, а все остальные - на 0x1042xxxx.

$ ./1 
[5/5]0x10432020 
[5/5]0x10429f58 
[5/5]0x10429f44 
0x10429f40