2012-06-02 9 views
96

Ich bin neu in Go und ich erlebe ein bisschen kongiffre Dissonanz zwischen C-style-Stack-basierte Programmierung, wo automatische Variablen auf dem Stapel leben und reservierte Speicherlevels auf dem Heap und Python-stack-based-Programmierung, wobei das einzige, was auf dem Stack lebt, Referenzen/Zeiger auf Objekte auf dem Heap sind.Stapel vs Heap Zuweisung von Strukturen in Go, und wie sie sich auf Garbage Collection beziehen

Soweit ich das beurteilen kann, geben die beiden folgenden Funktionen die gleiche Ausgabe:

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

    ... 

    return chunk, nil 
} 


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

    ... 

    return &chunk, nil 
} 

das heißt eine neue Struktur zuweisen und es zurück.

Wenn ich das in C geschrieben hätte, hätte der erste ein Objekt auf den Haufen gelegt, und der zweite hätte es auf den Stapel gelegt. Der erste würde einen Zeiger auf den Heap zurückgeben, der zweite würde einen Zeiger auf den Stack zurückgeben, der zu der Zeit, zu der die Funktion zurückgekehrt wäre, verdampft wäre, was ein schlechtes Ding wäre.

Wenn ich es in Python (oder vielen anderen modernen Sprachen außer C#) geschrieben hätte, wäre Beispiel 2 nicht möglich gewesen.

Ich bekomme, dass Go Garbage beide Werte sammelt, so sind beide der obigen Formen in Ordnung.

zu zitieren:

Beachten Sie, dass, anders als in C, es vollkommen in Ordnung ist, die Adresse eines lokalen Variablen zurückzukehren; Der mit der Variablen verknüpfte Speicher überlebt , nachdem die Funktion zurückgegeben wurde. In der Tat, die Adresse eines zusammengesetzten Literal nimmt eine neue Instanz jedes Mal zuweist, wenn es ausgewertet wird, so dass wir diese letzten zwei Zeilen kombinieren können.

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

Aber es wirft ein paar Fragen.

1 - In Beispiel 1 wird die Struktur auf dem Heap deklariert. Was ist mit Beispiel 2? Wird das auf dem Stapel genauso deklariert wie in C oder geht es auch auf den Haufen?

2 - Wenn Beispiel 2 auf dem Stack deklariert ist, wie bleibt es nach der Rückkehr der Funktion verfügbar?

3 - Wenn Beispiel 2 tatsächlich auf dem Heap deklariert ist, wie werden Strukturen nach Wert und nicht nach Referenz übergeben? Was ist der Sinn von Zeigern in diesem Fall?

Antwort

111

Es ist erwähnenswert, dass die Wörter "stack" und "heap" nirgendwo in der Sprachspezifikation erscheinen. Ihre Frage lautet wie folgt: "... wird auf dem Stapel deklariert" und "... auf dem Heap deklariert". Beachten Sie jedoch, dass die Go-Deklarationssyntax nichts über Stack oder Heap aussagt.

Das technisch macht die Antwort auf alle Ihre Fragen Implementierung abhängig. In Wirklichkeit gibt es natürlich einen Stapel (pro Goroutine!) Und einen Haufen und einige Dinge gehen auf den Stapel und einige auf den Haufen. In einigen Fällen folgt der Compiler strengen Regeln (wie " wird immer auf dem Heap zugewiesen") und in anderen Fällen führt der Compiler "Escape-Analyse" aus, um zu entscheiden, ob ein Objekt auf dem Stack leben kann oder ob es auf dem Heap zugewiesen werden muss.

In Ihrem Beispiel 2 würde die Escape-Analyse den Zeiger auf die Struktur-Escaping zeigen und der Compiler müsste die Struktur zuweisen. Ich denke, die aktuelle Implementierung von Go folgt in diesem Fall einer starren Regel, die besagt, dass, wenn die Adresse von irgendeinem Teil einer Struktur genommen wird, die Struktur auf den Haufen geht.

Für Frage 3 riskieren wir, über Terminologie verwirrt zu werden. Alles in Go wird nach Wert übergeben, es gibt keinen Durchgang als Referenz. Hier geben Sie einen Zeigerwert zurück. Was ist der Sinn von Zeigern? Betrachten Sie die folgende Änderung Ihrer Beispiel:

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 
} 

I MyFunction2 modifiziert, um die Struktur zurückzukehren, anstatt die Adresse der Struktur. Vergleichen Sie die Montageleistung von MyFunction1 und MyFunction2 jetzt,

--- 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  , 

Sorgen Sie sich nicht, dass MyFunction1 Ausgang hier anders ist als in peterSO der (sehr zufrieden) Antwort. Wir laufen offensichtlich verschiedene Compiler. Andernfalls sehe ich, dass ich myFunction2 so geändert habe, dass myStructType zurückgegeben wird und nicht * myStructType. Der Aufruf von runtime.new ist weg, was in manchen Fällen eine gute Sache wäre. Halten Sie allerdings auf, hier ist 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  , 

Noch kein Aufruf runtime.new, und ja, es funktioniert wirklich ein 8 MB großes Objekt nach Wert zurückzukehren. Es funktioniert, aber normalerweise möchten Sie nicht. Der Sinn eines Zeigers wäre es hier zu vermeiden, 8MB Objekte zu verschieben.

+7

Ausgezeichneter Dank. Ich fragte nicht wirklich "was ist der Sinn von Zeigern überhaupt?", Es war mehr wie "was ist der Sinn von Zeigern, wenn Werte sich wie Zeiger zu verhalten scheinen", und dieser Fall wird sowieso durch Ihre Antwort irrelevant gemacht. – Joe

+7

Eine kurze Erklärung der Montage wäre willkommen. – 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 
} 

In beiden Fällen aktuelle Implementierungen von Go würden Speicher für ein struct vom Typ MyStructType auf einem Haufen zuweisen und seine Adresse zurück. Die Funktionen sind gleichwertig; Die Quelle des Compiler asm ist dieselbe.

--- 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 einem Funktionsaufruf werden die Funktionswert und Argumente in die übliche Reihenfolge ausgewertet. Nach der Auswertung werden die Parameter des Aufrufs als Wert an die Funktion übergeben und die aufgerufene Funktion beginnt mit der Ausführung .Die Rückgabe-Parameter der Funktion werden mit dem Wert an die aufrufende Funktion zurückgegeben, wenn die Funktion zurückkehrt.

Alle Funktions- und Rückgabeparameter werden als Wert übergeben. Der Rückgabeparameterwert mit dem Typ *MyStructType ist eine Adresse.

+0

Vielen Dank! Upvoted, aber ich akzeptiere Sonias wegen der bisschen Fluchtanalyse. – Joe

+0

peterSo, wie produzieren Sie und @Sonia diese Baugruppe? Sie haben beide die gleiche Formatierung. Ich kann es nicht unabhängig von Befehl/Flags, objdump, go tool, otool versucht haben. –

+0

Ah, hab's - gcflags. –

10

Nach Go's FAQ:

wenn der Compiler nicht nachweisen kann, dass die Variable nicht nach die Funktion zurückkehrt Bezug genommen wird, dann muss der Compiler die Variable zuweisen auf die Garbage Collection Heap baumelt Zeiger Fehler zu vermeiden .