Menu

Puntatori in Golang: & e *, new, nil e quando usarli

Un puntatore contiene l'indirizzo di un valore. Impara & e *, new, i puntatori a struct, perché puoi restituire in sicurezza un puntatore a una variabile locale, quando usare i puntatori e il panic da dereferenziazione di un puntatore nil.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

& e *

Un puntatore contiene l'indirizzo di memoria di un valore. &x ti dà un puntatore a x. *p segue il puntatore fino al valore.

L'asterisco ha due ruoli. In un tipo (*int) significa "puntatore a int". In un'espressione (*p) significa "il valore a cui punta p". Stampare p da solo mostra un indirizzo come 0xc000012345, che cambia da un'esecuzione all'altra.

Perché esistono i puntatori: condividere invece di copiare

Go passa tutto per valore. Una funzione riceve una copia di ogni argomento, quindi non può cambiare la variabile di chi la chiama. Passare un puntatore copia invece l'indirizzo, ed entrambe le parti raggiungono lo stesso valore.

Go non ha riferimenti nel senso del C++. In Go "passare per riferimento" significa passare un puntatore per valore.

Puntatori a struct

I puntatori a struct sono i puntatori più comuni nel codice Go. L'accesso ai campi dereferenzia in automatico: p.Name significa (*p).Name.

I metodi seguono la stessa logica: un receiver puntatore permette al metodo di cambiare la struct. La pagina sui metodi tratta la scelta tra receiver valore e receiver puntatore.

new

new(T) alloca un T azzerato e restituisce il suo indirizzo. È come dichiarare una variabile e prenderne l'indirizzo.

p := new(int) // *int pointing at 0
var x int
q := &x           // same thing, two lines
a := new(Account) // same as &Account{}

In pratica per le struct è più comune &T{...}, perché permette di impostare i campi nella stessa espressione. new è comodo per i puntatori a tipi di base. Non confonderlo con make, che costruisce slice, map e channel e li restituisce pronti all'uso, non come puntatori.

Restituire un puntatore a una variabile locale è sicuro

In C, restituire l'indirizzo di una variabile locale è un bug. In Go è codice normale.

L'escape analysis del compilatore decide se un valore vive sullo stack o sull'heap. Non scegli mai tu, e non liberi mai la memoria: il garbage collector la recupera quando niente vi punta più. go build -gcflags=-m stampa queste decisioni, se sei curioso.

Puntatori nil

Lo zero value di qualsiasi tipo puntatore è nil. Dereferenziare nil va in panic.

Output:

1
2
true
recovered: runtime error: invalid memory address or nil pointer dereference

Senza il recover, il programma crasherebbe con quel messaggio e uno stack trace che inizia con panic: runtime error: invalid memory address or nil pointer dereference e [signal SIGSEGV: segmentation violation ...]. Le fonti abituali:

  • una variabile puntatore o un campo di una struct mai impostati
  • m[key] su una map[string]*T per una chiave mancante, che restituisce nil
  • ignorare un errore: f, _ := os.Open(path) lascia f a nil in caso di fallimento
  • un metodo con receiver puntatore chiamato su un puntatore nil, che poi legge un campo

Chiamare un metodo su un puntatore nil è di per sé permesso. Il panic avviene solo quando il metodo tocca i campi del receiver.

Quando usare un puntatore

Usa un puntatore quando:

  • una funzione o un metodo deve cambiare il valore di chi chiama
  • il valore è una struct grande e viene passato in giro spesso
  • il tipo non deve essere copiato (contiene un sync.Mutex, o rappresenta una risorsa unica come una connessione)
  • ti serve esprimere "nessun valore" con nil, per esempio un campo facoltativo in una struct JSON (*int distingue "assente" da 0)

Non usare un puntatore quando:

  • il valore è piccolo e di sola lettura (time.Time, un Point)
  • il tipo si comporta già come un riferimento: slice, map, channel, funzioni e interface raramente hanno bisogno di un * davanti. Un *[]int o una *map[string]int sono quasi sempre un errore

I puntatori non sono automaticamente più veloci. Copiare una struct piccola costa poco, e un puntatore può forzare un'allocazione sull'heap e aggiungere lavoro al garbage collector.

Puntatori e variabili del ciclo

Prendere l'indirizzo di una variabile del ciclo è sicuro da Go 1.22, perché ogni iterazione ha la sua variabile:

Stampa 0 1 2. Prima di Go 1.22 (che ha introdotto anche range 3), lo stesso ciclo scritto come for i := 0; i < 3; i++ stampava 3 3 3, perché tutti e tre i puntatori condividevano una sola variabile. Nota che &v in for _, v := range items punta a una copia dell'elemento, non all'elemento nella slice. Per avere un puntatore all'elemento stesso, usa &items[i].

Errori comuni

  • Dereferenziare senza controllare. Controlla if p == nil ovunque nil sia possibile.
  • Puntatore al valore di un range. &v punta a una copia; usa &s[i].
  • Puntatori a slice e map. Servono raramente. Restituisci invece la nuova slice.
  • Tenere &s[i] attraverso un append. Se append rialloca, il puntatore fa ancora riferimento al vecchio array e le scritture successive finiscono in un posto che nessuno vede.

Domande frequenti

Cosa significano & e * in Go?

&x prende l'indirizzo di x e dà un puntatore di tipo *T. *p dereferenzia il puntatore: legge o scrive il valore a cui punta. In un tipo, *T significa "puntatore a T".

Qual è la differenza tra new e make in Go?

new(T) alloca un T azzerato e restituisce un *T; funziona con qualsiasi tipo. make funziona solo con slice, map e channel, e restituisce un valore inizializzato (non azzerato, non un puntatore) di quel tipo: make(map[string]int) è pronto all'uso, mentre new(map[string]int) è un puntatore a una map nil.

Posso restituire un puntatore a una variabile locale in Go?

Sì. A differenza del C, è sicuro. L'escape analysis del compilatore vede che la variabile sopravvive alla funzione e la alloca sull'heap. func newInt() *int { x := 5; return &x } è Go corretto.

Cosa causa "invalid memory address or nil pointer dereference" in Go?

Leggere o scrivere attraverso un puntatore nil: una variabile *T mai assegnata, la ricerca di una chiave mancante in una map[K]*V, o una funzione che ha restituito nil insieme a un errore che non hai controllato. Controlla se è nil, oppure inizializza il puntatore prima di usarlo.

Go ha l'aritmetica dei puntatori?

No. Non puoi sommare numeri a un puntatore né indicizzare la memoria attraverso di esso. Il package unsafe lo permette per il codice di basso livello, senza garanzie di sicurezza. Il codice Go normale usa le slice per la memoria contigua.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA