& und *
Ein Pointer enthält die Speicheradresse eines Werts. &x liefert dir einen Pointer auf x. *p folgt dem Pointer zum Wert.
Der Stern hat zwei Rollen. In einem Typ (*int) bedeutet er „Pointer auf int“. In einem Ausdruck (*p) bedeutet er „der Wert, auf den p zeigt“. Gibst du p selbst aus, siehst du eine Adresse wie 0xc000012345, die sich von Lauf zu Lauf unterscheidet.
Wozu Pointer da sind: teilen statt kopieren
Go übergibt alles als Wert. Eine Funktion bekommt eine Kopie jedes Arguments und kann die Variable des Aufrufers daher nicht ändern. Übergibst du einen Pointer, wird stattdessen die Adresse kopiert, und beide Seiten erreichen denselben Wert.
Go hat keine Referenzen im Sinne von C++. „Pass by reference“ bedeutet in Go, einen Pointer als Wert zu übergeben.
Pointer auf Structs
Pointer auf Structs sind die häufigsten Pointer in Go-Code. Der Feldzugriff dereferenziert automatisch: p.Name bedeutet (*p).Name.
Methoden folgen derselben Logik: Ein Pointer Receiver erlaubt der Methode, das Struct zu ändern. Die Seite zu Methoden behandelt die Wahl zwischen Value und Pointer Receiver.
new
new(T) legt ein genulltes T an und gibt seine Adresse zurück. Das ist dasselbe, wie eine Variable zu deklarieren und ihre Adresse zu nehmen.
p := new(int) // *int pointing at 0
var x int
q := &x // same thing, two lines
a := new(Account) // same as &Account{}
In der Praxis ist &T{...} bei Structs verbreiteter, weil du damit im selben Ausdruck Felder setzen kannst. new ist praktisch für Pointer auf Basistypen. Verwechsle es nicht mit make, das Slices, Maps und Channels baut und sie einsatzbereit zurückgibt, nicht als Pointer.
Einen Pointer auf eine lokale Variable zurückzugeben ist sicher
In C ist es ein Bug, die Adresse einer lokalen Variablen zurückzugeben. In Go ist es gewöhnlicher Code.
Die Escape-Analyse des Compilers entscheidet, ob ein Wert auf dem Stack oder auf dem Heap lebt. Du wählst das nie, und du gibst nie Speicher frei: Der Garbage Collector holt ihn zurück, wenn nichts mehr darauf zeigt. go build -gcflags=-m gibt diese Entscheidungen aus, falls es dich interessiert.
nil-Pointer
Der Nullwert jedes Pointer-Typs ist nil. nil zu dereferenzieren löst eine Panic aus.
Ausgabe:
1
2
true
recovered: runtime error: invalid memory address or nil pointer dereference
Ohne das recover würde das Programm mit dieser Meldung und einem Stacktrace abstürzen, der mit panic: runtime error: invalid memory address or nil pointer dereference und [signal SIGSEGV: segmentation violation ...] beginnt. Die üblichen Ursachen:
- eine Pointer-Variable oder ein Struct-Feld, das nie gesetzt wurde
m[key]auf einermap[string]*Tfür einen fehlenden Schlüssel, wasnilliefert- ein ignorierter Fehler:
f, _ := os.Open(path)lässtfbei einem Fehlschlag nil - eine Methode mit Pointer Receiver, die auf einem nil-Pointer aufgerufen wird und dann ein Feld liest
Eine Methode auf einem nil-Pointer aufzurufen ist an sich erlaubt. Die Panic passiert erst, wenn die Methode auf die Felder des Receivers zugreift.
Wann du einen Pointer nimmst
Nimm einen Pointer, wenn:
- eine Funktion oder Methode den Wert des Aufrufers ändern muss
- der Wert ein großes Struct ist und oft herumgereicht wird
- der Typ nicht kopiert werden darf (er enthält einen
sync.Mutexoder steht für eine eindeutige Ressource wie eine Verbindung) - du mit
nil„kein Wert“ ausdrücken musst, zum Beispiel ein optionales Feld in einem JSON-Struct (*intunterscheidet „fehlt“ von0)
Nimm keinen Pointer, wenn:
- der Wert klein ist und nur gelesen wird (
time.Time, einPoint) - der Typ schon ein referenzartiger Wert ist: Slices, Maps, Channels, Funktionen und Interfaces brauchen selten ein
*davor. Ein*[]intoder*map[string]intist fast immer ein Fehler
Pointer sind nicht automatisch schneller. Ein kleines Struct zu kopieren ist billig, und ein Pointer kann eine Heap-Allokation erzwingen und dem Garbage Collector Arbeit machen.
Pointer und Schleifenvariablen
Die Adresse einer Schleifenvariablen zu nehmen ist seit Go 1.22 sicher, weil jeder Durchlauf seine eigene Variable hat:
Das gibt 0 1 2 aus. Vor Go 1.22 (das auch range 3 eingeführt hat) gab dieselbe Schleife, geschrieben als for i := 0; i < 3; i++, 3 3 3 aus, weil sich alle drei Pointer eine Variable teilten. Beachte, dass &v in for _, v := range items auf eine Kopie des Elements zeigt, nicht auf das Element im Slice. Einen Pointer auf das Element selbst bekommst du mit &items[i].
Häufige Fehler
- Dereferenzieren ohne Prüfung. Prüf
if p == nilüberall, wo nil möglich ist. - Pointer auf einen range-Wert.
&vzeigt auf eine Kopie; nimm&s[i]. - Pointer auf Slices und Maps. Selten nötig. Gib stattdessen den neuen Slice zurück.
&s[i]über einappendhinweg halten. Legtappendneu an, verweist der Pointer weiter auf das alte Array, und spätere Schreibzugriffe landen nirgends, wo man sie sieht.
Häufig gestellte Fragen
Was bedeuten & und * in Go?
&x nimmt die Adresse von x und liefert einen Pointer vom Typ *T. *p dereferenziert den Pointer: Es liest oder schreibt den Wert, auf den er zeigt. In einem Typ bedeutet *T „Pointer auf T“.
Was ist der Unterschied zwischen new und make in Go?
new(T) legt ein genulltes T an und gibt ein *T zurück; es funktioniert mit jedem Typ. make funktioniert nur mit Slices, Maps und Channels und gibt einen initialisierten Wert dieses Typs zurück (nicht genullt, kein Pointer): make(map[string]int) ist sofort einsatzbereit, während new(map[string]int) ein Pointer auf eine nil-Map ist.
Kann ich in Go einen Pointer auf eine lokale Variable zurückgeben?
Ja. Anders als in C ist das sicher. Die Escape-Analyse des Compilers erkennt, dass die Variable die Funktion überlebt, und legt sie auf dem Heap an. func newInt() *int { x := 5; return &x } ist korrektes Go.
Was verursacht „invalid memory address or nil pointer dereference“ in Go?
Lesen oder Schreiben über einen nil-Pointer: eine *T-Variable, die nie zugewiesen wurde, ein Map-Lookup eines fehlenden Schlüssels in einer map[K]*V oder eine Funktion, die nil zusammen mit einem Fehler zurückgegeben hat, den du nicht geprüft hast. Prüf auf nil oder initialisier den Pointer vor der Verwendung.
Hat Go Pointer-Arithmetik?
Nein. Du kannst nichts zu einem Pointer addieren und keinen Speicher darüber indizieren. Das Paket unsafe erlaubt es für Low-Level-Code, ohne jede Sicherheitsgarantie. Normaler Go-Code nutzt Slices für zusammenhängenden Speicher.