& et *
Un pointeur contient l'adresse mémoire d'une valeur. &x vous donne un pointeur vers x. *p suit le pointeur jusqu'à la valeur.
L'étoile a deux rôles. Dans un type (*int), elle signifie « pointeur vers int ». Dans une expression (*p), elle signifie « la valeur vers laquelle p pointe ». Afficher p lui-même montre une adresse comme 0xc000012345, qui change d'une exécution à l'autre.
Pourquoi les pointeurs existent : partager au lieu de copier
Go passe tout par valeur. Une fonction reçoit une copie de chaque argument, donc elle ne peut pas modifier la variable de l'appelant. Passer un pointeur copie l'adresse à la place, et les deux côtés atteignent la même valeur.
Go n'a pas de références au sens de C++. En Go, « passer par référence » signifie passer un pointeur par valeur.
Pointeurs vers des structs
Les pointeurs de structs sont les pointeurs les plus courants dans le code Go. L'accès aux champs déréférence automatiquement : p.Name signifie (*p).Name.
Les méthodes suivent la même logique : un receveur pointeur permet à la méthode de modifier la struct. La page sur les méthodes traite le choix entre receveur valeur et receveur pointeur.
new
new(T) alloue un T mis à zéro et renvoie son adresse. C'est la même chose que déclarer une variable et prendre son adresse.
p := new(int) // *int pointing at 0
var x int
q := &x // same thing, two lines
a := new(Account) // same as &Account{}
En pratique, &T{...} est plus courant pour les structs parce qu'il permet de définir les champs dans la même expression. new est pratique pour des pointeurs vers des types de base. Ne le confondez pas avec make, qui construit des slices, maps et channels et les renvoie prêts à l'emploi, pas sous forme de pointeurs.
Renvoyer un pointeur vers une variable locale est sûr
En C, renvoyer l'adresse d'une variable locale est un bug. En Go, c'est du code ordinaire.
L'analyse d'échappement du compilateur décide si une valeur vit sur la pile ou sur le tas. Vous ne choisissez jamais, et vous ne libérez jamais la mémoire : le ramasse-miettes la récupère quand plus rien ne pointe dessus. go build -gcflags=-m affiche ces décisions si vous êtes curieux.
Pointeurs nil
La valeur zéro de tout type pointeur est nil. Déréférencer nil provoque un panic.
Sortie :
1
2
true
recovered: runtime error: invalid memory address or nil pointer dereference
Sans le recover, le programme planterait avec ce message et une trace de pile commençant par panic: runtime error: invalid memory address or nil pointer dereference et [signal SIGSEGV: segmentation violation ...]. Les sources habituelles :
- une variable pointeur ou un champ de struct jamais défini
m[key]sur unemap[string]*Tpour une clé absente, qui renvoienil- une erreur ignorée :
f, _ := os.Open(path)laissefà nil en cas d'échec - une méthode à receveur pointeur appelée sur un pointeur nil, qui lit ensuite un champ
Appeler une méthode sur un pointeur nil est en soi légal. Le panic ne se produit que lorsque la méthode touche les champs du receveur.
Quand utiliser un pointeur
Utilisez un pointeur quand :
- une fonction ou une méthode doit modifier la valeur de l'appelant
- la valeur est une grosse struct qui circule souvent
- le type ne doit pas être copié (il contient un
sync.Mutex, ou il représente une ressource unique comme une connexion) - vous devez exprimer « pas de valeur » avec
nil, par exemple un champ facultatif dans une struct JSON (*intdistingue « absent » de0)
N'utilisez pas de pointeur quand :
- la valeur est petite et en lecture seule (
time.Time, unPoint) - le type se comporte déjà comme une référence : les slices, maps, channels, fonctions et interfaces ont rarement besoin d'un
*devant. Un*[]intou un*map[string]intest presque toujours une erreur
Les pointeurs ne sont pas automatiquement plus rapides. Copier une petite struct ne coûte presque rien, et un pointeur peut forcer une allocation sur le tas et ajouter du travail au ramasse-miettes.
Pointeurs et variables de boucle
Prendre l'adresse d'une variable de boucle est sûr depuis Go 1.22, parce que chaque itération a sa propre variable :
Ce code affiche 0 1 2. Avant Go 1.22 (qui a aussi introduit range 3), la même boucle écrite for i := 0; i < 3; i++ affichait 3 3 3, puisque les trois pointeurs partageaient une seule variable. Notez que &v dans for _, v := range items pointe vers une copie de l'élément, pas vers l'élément de la slice. Pour obtenir un pointeur vers l'élément lui-même, utilisez &items[i].
Erreurs courantes
- Déréférencer sans vérifier. Testez
if p == nilpartout où nil est possible. - Un pointeur vers une valeur de range.
&vpointe vers une copie ; utilisez&s[i]. - Des pointeurs vers des slices et des maps. Rarement nécessaires. Renvoyez plutôt la nouvelle slice.
- Garder
&s[i]à travers unappend. Siappendréalloue, le pointeur fait toujours référence à l'ancien tableau et les écritures suivantes ne sont visibles nulle part.
Questions fréquentes
Que signifient & et * en Go ?
&x prend l'adresse de x et donne un pointeur de type *T. *p déréférence le pointeur : il lit ou écrit la valeur pointée. Dans un type, *T signifie « pointeur vers T ».
Quelle est la différence entre new et make en Go ?
new(T) alloue un T mis à zéro et renvoie un *T ; cela fonctionne pour n'importe quel type. make ne fonctionne que pour les slices, les maps et les channels, et renvoie une valeur initialisée (pas mise à zéro, et pas un pointeur) de ce type : make(map[string]int) est prête à l'emploi, alors que new(map[string]int) est un pointeur vers une map nil.
Peut-on renvoyer un pointeur vers une variable locale en Go ?
Oui. Contrairement au C, c'est sûr. L'analyse d'échappement du compilateur voit que la variable survit à la fonction et l'alloue sur le tas. func newInt() *int { x := 5; return &x } est du Go correct.
Qu'est-ce qui cause « invalid memory address or nil pointer dereference » en Go ?
Lire ou écrire via un pointeur nil : une variable *T jamais affectée, la recherche d'une clé absente dans une map[K]*V, ou une fonction qui a renvoyé nil avec une erreur que vous n'avez pas vérifiée. Testez nil, ou initialisez le pointeur avant de l'utiliser.
Go a-t-il l'arithmétique de pointeurs ?
Non. Vous ne pouvez ni ajouter une valeur à un pointeur ni indexer la mémoire à travers lui. Le package unsafe le permet pour le code bas niveau, sans aucune garantie de sûreté. Le code Go normal utilise des slices pour la mémoire contiguë.