Menu

Golang Pointer: & und *, new, nil und wann du sie brauchst

Ein Pointer enthält die Adresse eines Werts. & und *, new, Pointer auf Structs, warum du gefahrlos einen Pointer auf eine lokale Variable zurückgeben kannst, wann sich Pointer lohnen und die Panic bei nil pointer dereference.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

& 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 einer map[string]*T für einen fehlenden Schlüssel, was nil liefert
  • ein ignorierter Fehler: f, _ := os.Open(path) lässt f bei 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.Mutex oder 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 (*int unterscheidet „fehlt“ von 0)

Nimm keinen Pointer, wenn:

  • der Wert klein ist und nur gelesen wird (time.Time, ein Point)
  • der Typ schon ein referenzartiger Wert ist: Slices, Maps, Channels, Funktionen und Interfaces brauchen selten ein * davor. Ein *[]int oder *map[string]int ist 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. &v zeigt auf eine Kopie; nimm &s[i].
  • Pointer auf Slices und Maps. Selten nötig. Gib stattdessen den neuen Slice zurück.
  • &s[i] über ein append hinweg halten. Legt append neu 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.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S