Penser à un pointeur comme à une adresse est un approximation . Comme toutes les approximations, elle est suffisamment bonne pour être utile parfois, mais elle n'est pas non plus exacte, ce qui signifie que s'y fier peut poser problème.
Un pointeur est comme une adresse dans la mesure où il indique où trouver un objet. Une limitation immédiate de cette analogie est que tous les pointeurs ne contiennent pas réellement une adresse. NULL
est un pointeur qui n'est pas une adresse. Le contenu d'une variable pointeur peut en fait être de l'une des trois sortes suivantes :
- le site adresse d'un objet, qui peut être déréférencé (si
p
contient l'adresse de x
alors l'expression *p
a la même valeur que x
) ;
- a pointeur nul dont
NULL
est un exemple ;
-
invalide qui ne pointe pas vers un objet (si
p
ne contient pas de valeur valide, alors *p
pourrait faire n'importe quoi ("comportement indéfini"), le plantage du programme étant une possibilité assez courante).
En outre, il serait plus exact de dire qu'un pointeur (s'il est valide et non nul) contient une adresse : un pointeur indique où trouver un objet, mais il est lié à d'autres informations.
En particulier, un pointeur a un type. Sur la plupart des plateformes, le type du pointeur n'a aucune influence au moment de l'exécution, mais il a une influence qui va au-delà du type au moment de la compilation. Si p
est un pointeur vers int
( int *p;
), alors p + 1
pointe vers un nombre entier qui est sizeof(int)
octets après p
(en supposant que p + 1
est toujours un pointeur valide). Si q
est un pointeur vers char
qui pointe vers la même adresse que p
( char *q = p;
), alors q + 1
n'est pas la même adresse que p + 1
. Si vous considérez les pointeurs comme des adresses, il n'est pas très intuitif que la "prochaine adresse" soit différente pour différents pointeurs vers le même emplacement.
Dans certains environnements, il est possible d'avoir plusieurs valeurs de pointeur avec des représentations différentes (différents modèles de bits en mémoire) qui pointent vers le même emplacement en mémoire. Vous pouvez penser à ces valeurs comme à différents pointeurs détenant la même adresse, ou comme à différentes adresses pour le même emplacement - la métaphore n'est pas claire dans ce cas. Le site ==
vous indique toujours si les deux opérandes pointent vers le même endroit, donc sur ces environnements vous pouvez avoir p == q
même si p
y q
ont des schémas binaires différents.
Il existe même des environnements où les pointeurs portent d'autres informations que l'adresse, comme le type ou les informations de permission. Vous pouvez facilement passer votre vie de programmeur sans les rencontrer.
Il existe des environnements où différents types de pointeurs ont des représentations différentes. C'est un peu comme si différents types d'adresses avaient des représentations différentes. Par exemple, certaines architectures ont des pointeurs d'octets et des pointeurs de mots, ou des pointeurs d'objets et des pointeurs de fonctions.
En somme, considérer les pointeurs comme des adresses n'est pas si mal, tant que vous gardez à l'esprit que
- ce ne sont que les pointeurs valides et non nuls qui sont des adresses ;
- vous pouvez avoir plusieurs adresses pour un même lieu ;
- on ne peut pas faire d'arithmétique sur les adresses, et il n'y a pas d'ordre sur elles ;
- le pointeur porte également des informations sur le type.
L'inverse est bien plus gênant. Tout ce qui ressemble à une adresse ne peut pas être un pointeur. . Quelque part, au fond, tout pointeur est représenté par un motif de bits qui peut être lu comme un nombre entier, et on peut dire que ce nombre entier est une adresse. Mais dans l'autre sens, tous les entiers ne sont pas des pointeurs.
Il y a d'abord quelques limitations bien connues ; par exemple, un nombre entier qui désigne un emplacement en dehors de l'espace d'adressage de votre programme ne peut pas être un pointeur valide. Une adresse mal alignée ne constitue pas un pointeur valide pour un type de données qui nécessite un alignement ; par exemple, sur une plateforme où int
nécessite un alignement sur 4 octets, 0x7654321 ne peut pas être une valeur valide. int*
valeur.
Cependant, cela va bien au-delà, car lorsque vous transformez un pointeur en un nombre entier, vous vous exposez à de nombreux problèmes. Une grande partie de ces problèmes est due au fait que les compilateurs optimisateurs sont bien meilleurs en micro-optimisation que ce à quoi la plupart des programmeurs s'attendent, de sorte que leur modèle mental du fonctionnement d'un programme est profondément erroné. Ce n'est pas parce que vous avez des pointeurs avec la même adresse qu'ils sont équivalents. Par exemple, considérez le bout de phrase suivant :
unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);
On pourrait s'attendre à ce que sur une machine ordinaire où sizeof(int)==4
y sizeof(short)==2
ce qui permet soit d'imprimer 1 = 1?
(little-endian) ou 65536 = 1?
(big-endian). Mais sur mon PC Linux 64 bits avec GCC 4.4 :
$ c99 -O2 -Wall a.c && ./a.out
a.c: In function ‘main’:
a.c:6: warning: dereferencing pointer ‘p’ does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?
GCC a la gentillesse de nous avertir de ce qui ne va pas dans cet exemple simple - dans des exemples plus complexes, le compilateur pourrait ne pas le remarquer. Puisque p
a un type différent de &x
en changeant ce qui p
ne peut pas affecter ce que &x
pointe (en dehors de certaines exceptions bien définies). Par conséquent, le compilateur est libre de garder la valeur de x
dans un registre et ne pas mettre à jour ce registre comme *p
changements. Le programme déréférence deux pointeurs à la même adresse et obtient deux valeurs différentes !
La morale de cet exemple est que l'on peut considérer un pointeur (valide et non nul) comme une adresse, tant que l'on respecte les règles précises du langage C. Le revers de la médaille est que les règles du langage C sont complexes et difficiles à appréhender intuitivement, à moins de savoir ce qui se passe sous le capot. Et ce qui se passe sous le capot est que le lien entre les pointeurs et les adresses est quelque peu relâché, à la fois pour supporter les architectures de processeurs "exotiques" et pour supporter les compilateurs optimisateurs.
Pensez donc que les pointeurs sont des adresses comme une première étape dans votre compréhension, mais ne suivez pas cette intuition trop loin.
127 votes
Un pointeur est un variable que tient une adresse. Il possède également son propre adresse. C'est la différence fondamentale entre un pointeur et un tableau. Un tableau est effectivement es une adresse (et par implication, son adresse est lui-même ).
3 votes
@WhozCraig En effet, mais la citation tient particulièrement à noter que
&
ne renvoie pas une adresse mémoire réelle. Que renvoie-t-il alors ?0 votes
programmeurs.stackexchange.com/questions/17898/
8 votes
Quelle est votre "source réputée" pour la citation ?
0 votes
Le pointeur n'a pas d'adresse, le pointeur est une valeur d'une "variable pointeur" qui a une adresse. Mais c'est bien, donc nous avons des pointeurs comme valeurs de pointeur et des pointeurs comme variables de pointeur. Rien d'autre pour embrouiller encore plus les débutants ?
3 votes
Le pointeur d'@exebook a une adresse parce que le pointeur est aussi une variable qui contient une adresse.
2 votes
@exebook Vraiment ? Le pointeur n'a pas d'adresse ? Mkk.
int *p = NULL, **pp = &p;
Tant pis pour ça.0 votes
@Cornstalks Mike Banahan, auteur de "The C Book". Également disponible @ publications.gbdirect.co.uk/c_livre/chapitre5/pointeurs.html
0 votes
@WhozCraig que diriez-vous de @NULL ? NULL est aussi un pointeur, mais est-ce une variable ? Que pensez-vous de (char*)55555 ? Est-ce un pointeur, a-t-il une adresse ?
0 votes
@exebook NULL n'est pas un pointeur. Techniquement, ce n'est même pas une adresse.
1 votes
@WhozCraig pourquoi l'appellent-ils "null pointer exception" alors ?
1 votes
@d0rmLife Oui, je pense que l'auteur a besoin de prendre du recul et de descendre du pilier. Sa logique pour continuer à utiliser un terme qu'il a si fraîchement attaqué auparavant est risible. Et en ce qui concerne la définition : C99 6.5.3.2,p3 : L'opérateur unaire & donne le résultat suivant l'adresse de son opérande." Le site type d'adresse renvoyée est spécifique à la variable à laquelle elle est appliquée, mais ce n'est pas une erreur si la norme utilise ce langage.
0 votes
@exebook vous êtes confus. Tous vos exemples ont une adresse. Où et comment, c'est une autre discussion. Et oui, les pointeurs ont adresses.
1 votes
L'auteur aurait pu faire une sacrée démonstration de la confusion entre adresses et les variables qui les contiennent (i.e. pointeurs) juste en disséquant les commentaires d'exebook dans cette question.
0 votes
Pourquoi chaque réponse suppose qu'une adresse mémoire est un entier... où est-il dit que cela doit être vrai ? il y a quelque chose de plus profond dans un pointeur qui fait qu'il n'est pas une adresse au-delà du fait qu'il n'a pas besoin d'être un entier.
23 votes
L'ultime source fiable est la norme linguistique, et non les livres qui en sont semi-dérivés et semi-pulsés du cul de l'auteur. Je l'ai appris à la dure, en faisant presque toutes les erreurs possibles et en construisant lentement un modèle mental de C assez proche de celui décrit par la norme, pour finalement remplacer ce modèle par celui de la norme.
0 votes
J'aimerais que les normes linguistiques soient écrites de manière à ce que les débutants puissent apprendre la langue en les utilisant. Aujourd'hui, on ne peut généralement réapprendre une langue que l'on connaît déjà qu'en lisant les normes.
9 votes
@thang Les gens pensent que pointeur=entier parce que c'est souvent le cas (Linux x86 et Windows nous "apprennent" cela), parce que les gens aiment généraliser, parce que les gens ne connaissent pas bien le standard du langage et parce qu'ils ont peu d'expérience avec des plateformes radicalement différentes. Ces mêmes personnes sont susceptibles de supposer qu'un pointeur vers une donnée et un pointeur vers une fonction peuvent être convertis l'un en l'autre et que les données peuvent être exécutées comme du code et le code être accédé comme des données. Cela peut être vrai sur les architectures de von Neuman (avec un seul espace d'adressage), mais pas nécessairement sur les architectures de Harvard (avec des espaces de code et de données).
1 votes
@AlexeyFrunze, la question est pourquoi toutes les réponses supposent qu'une adresse mémoire est un nombre entier ? et votre réponse est Les gens pensent que le pointeur est un nombre entier parce que c'est souvent le cas. . vous voyez ce qui ne va pas ici ? vous supposez implicitement que pointeur=adresse mémoire par Gricean...
6 votes
@exebook Les normes ne sont pas pour les débutants (surtout, les complets). Elles ne sont pas censées fournir des introductions douces et des multitudes d'exemples. Elles définissent formellement quelque chose, afin qu'il puisse être correctement mis en œuvre par des professionnels.
1 votes
@thang Je pense avoir été clair dans ma réponse et dans mes commentaires.
0 votes
Un pointeur de pile est également un pointeur, mais il n'a pas d'adresse. En résumé, un pointeur est un "concept de référence à quelque chose avec son adresse".
1 votes
NULL n'est pas une adresse valide, mais NULL peut être la valeur d'une variable pointeur.
1 votes
Plusieurs personnes ont déclaré qu'un pointeur est une variable. D'autres ne sont pas d'accord (@Aki - stack pointer ; fr.wikipedia.org/wiki/Pointer_%28programmation_informatique%29 "un type de données"). Quelqu'un peut-il citer une référence faisant autorité ? Merci.
1 votes
@WhozCraig, une citation pour appuyer votre commentaire initial (en particulier, l'idée qu'un pointeur doit être une variable) ? J'essaie juste d'apprendre si c'est l'opinion de quelques personnes ou un fait établi. Par exemple, la page man 3 fopen() dit que la fonction renvoie un pointeur FILE, alors qu'il est évident qu'elle ne renvoie pas une variable. La page de manuel est-elle erronée ?
0 votes
@kutschkem NULL est une adresse valide sur certains systèmes d'exploitation (ou absence de systèmes d'exploitation). Ce n'est pas parce que le système d'exploitation sur lequel vous travaillez vous donne une violation d'accès lorsque vous essayez de lire/écrire NULL que cela signifie que ce n'est jamais une adresse valide.
0 votes
@Zach Selon la norme, déférencer NULL est un comportement non défini, en C++ au moins : stackoverflow.com/questions/4364536/c-null-reference . Il semble que ce soit une très mauvaise décision d'avoir 0 comme pointeur valide.
0 votes
Lorsque l'on supprime le système d'exploitation et que l'on dispose simplement d'un espace d'adressage de la taille du nombre de bits représentant toutes les valeurs de pointeur, comme dans le Microblaze de Xilinx, l'endroit où l'on place les choses dans cet espace n'a pas d'importance. Cependant, NULL peut être défini comme étant 0xFFFFFFFF sur cette plate-forme si l'on n'affecte rien à cet emplacement dans l'espace d'adressage, donc 0 peut être un pointeur valide puisque NULL ne doit pas nécessairement être 0.
0 votes
Duplicata possible de Détails de l'implémentation des pointeurs en C
0 votes
@d0rmLife Je pense que vous pouvez maintenant accepter une réponse. La question est ouverte depuis presque deux mois et a reçu suffisamment d'attention.
0 votes
"La notion C++ (et C) de tableau et de pointeur sont des représentations directes de la notion de mémoire et d'adresses d'une machine, fournies sans surcharge." ~ B. Stroustrup