web-dev-qa-db-fra.com

Qu'est-ce qu'un pointeur C, sinon une adresse mémoire?

Dans une source fiable sur C, les informations suivantes sont fournies après avoir discuté de la & opérateur:

... Il est un peu dommage que la terminologie [adresse de] demeure, car elle confond ceux qui ne savent pas de quelle adresse il s'agit et qui induit en erreur ceux qui le font: penser aux pointeurs comme à des adresses mène généralement au chagrin ...

D’autres documents que j’ai lus (de sources aussi réputées, dirais-je) ont toujours fait allusion sans vergogne aux pointeurs et au & opérateur en tant qu’adresse de mémoire. J'adorerais continuer à chercher l'actualité, mais c'est un peu difficile lorsque des sources dignes de confiance sont en désaccord.

Maintenant, je suis un peu confus - qu'est-ce que exactement est un pointeur, sinon une adresse mémoire?

P.S.

L’auteur dit plus tard: ... Je vais continuer à utiliser le terme "adresse de" bien, car en inventer un autre [terme] serait encore pire.

205
d0rmLife

La norme C ne définit pas ce qu'est un pointeur en interne ni son fonctionnement en interne. Ceci est intentionnel afin de ne pas limiter le nombre de plates-formes, où C peut être implémenté en tant que langage compilé ou interprété.

Une valeur de pointeur peut être une sorte d'identifiant ou de descripteur ou une combinaison de plusieurs identifiants (par exemple, bonjour les segments x86 et les décalages) et pas nécessairement une adresse mémoire réelle. Cet ID peut être n'importe quoi, même une chaîne de texte de taille fixe. Les représentations sans adresse peuvent être particulièrement utiles pour un interpréteur C.

146
Alexey Frunze

Je ne suis pas sûr de votre source, mais le type de langage que vous décrivez provient du standard C:

6.5.3.2 Opérateurs d'adresse et d'indirection
[...]
3. Le unaire et l'opérateur donne l'adresse de son opérande. [...]

Alors ... ouais, les pointeurs pointent vers des adresses de mémoire. Au moins, c'est ce que le standard C suggère.

Pour le dire un peu plus clairement, un pointeur est une variable contenant la valeur de quelque adresse . L'adresse d'un objet (qui peut être stocké dans un pointeur) est renvoyée avec le unaire & _ opérateur.

Je peux stocker l'adresse "42 Wallaby Way, Sydney" dans une variable (et cette variable serait un "pointeur", mais comme ce n'est pas une adresse mémoire, ce n'est pas quelque chose que nous appellerions correctement un "pointeur"). Votre ordinateur a des adresses pour ses seaux de mémoire. Les pointeurs stockent la valeur d'une adresse (c'est-à-dire qu'un pointeur stocke la valeur "42 Wallaby Way, Sydney", qui est une adresse).

Edit: Je veux développer le commentaire d'Alexey Frunze.

Qu'est-ce qu'un pointeur? Regardons le standard C:

Types 6.2.5
[...]
20. [...]
Un type de pointeur peut être dérivé d'un type de fonction ou d'un type d'objet, appelé le référencé tapez . Un type de pointeur décrit un objet dont la valeur fournit une référence à une entité du type référencé. Un type de pointeur dérivé du type référencé T est parfois appelé "pointeur vers T". La construction d’un type de pointeur à partir d’un type référencé est appelée "dérivation de type de pointeur". Un type de pointeur est un type d'objet complet.

Essentiellement, les pointeurs stockent une valeur qui fournit une référence à un objet ou à une fonction. Genre de. Les pointeurs sont destinés à stocker une valeur qui fournit une référence à un objet ou à une fonction, mais ce n'est pas toujours le cas:

6.3.2.3 Pointeurs
[...]
5. Un entier peut être converti en n'importe quel type de pointeur. Sauf indication contraire, le résultat est défini par l'implémentation, peut ne pas être correctement aligné, peut ne pas pointer vers une entité du type référencé et peut être une représentation d'interruption.

La citation ci-dessus indique que nous pouvons transformer un entier en un pointeur. Si nous faisons cela (c’est-à-dire si nous insérons une valeur entière dans un pointeur au lieu d’une référence spécifique à un objet ou à une fonction), le pointeur "pourrait ne pas pointer sur une entité de type référence" (c’est-à-dire ne pas fournir référence à un objet ou une fonction). Cela pourrait nous fournir autre chose. Et c’est un endroit où vous pouvez coller une sorte de pseudonyme ou d’ID dans un pointeur (c’est-à-dire que le pointeur ne pointe pas sur un objet; il stocke une valeur qui représente quelque chose, mais cette valeur peut ne pas être une adresse).

Alors oui, comme le dit Alexey Frunze, il est possible qu'un pointeur ne stocke pas d'adresse dans un objet ou une fonction. Il est possible qu'un pointeur stocke une sorte de "descripteur" ou d'identifiant, et vous pouvez le faire en attribuant une valeur entière arbitraire à un pointeur. Ce que représente ce descripteur ou cet ID dépend du système/de l'environnement/du contexte. Tant que votre système/implémentation peut donner un sens à la valeur, vous êtes en bonne forme (mais cela dépend de la valeur spécifique et du système/implémentation spécifique).

Normalement , un pointeur stocke une adresse vers un objet ou une fonction. S'il ne stocke pas une adresse réelle (vers un objet ou une fonction), le résultat est défini par l'implémentation (ce qui signifie exactement ce qui se produit et ce que représente le pointeur dépend maintenant de votre système et de son implémentation. Il peut donc s'agir d'un descripteur ou d'un ID un système particulier, mais l’utilisation du même code/de la même valeur sur un autre système peut planter votre programme).

Cela a fini par être plus long que je ne le pensais ...

62
Cornstalks

Pointer vs Variable

Sur cette photo,

pointer_p est un pointeur situé à 0x12345 et pointant vers une variable variable_v à 0x34567.

40
Harikrishnan

Considérer un pointeur comme une adresse est une approximation . Comme toutes les approximations, il est bon de servir parfois, mais ce n’est pas exact, ce qui signifie que s’y fier pose des problèmes.

Un pointeur est comme une adresse en ce sens qu'il indique où trouver un objet. Une limite 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 de pointeur peut en fait être de trois types:

  • l'adresse d'un objet qui peut être déréférencé (si p contient l'adresse de x, puis l'expression *p A la même valeur que x);
  • un pointeur null , dont NULL est un exemple;
  • invalide contenu, qui ne pointe pas vers un objet (si p ne contient pas de valeur valide, alors *p peut faire n'importe quoi ("comportement indéfini"), le crash du programme est une possibilité assez courante).

De plus, il serait plus précis de dire qu'un pointeur (si valide et non nul) contient une adresse: un pointeur indique où trouver un objet, mais il y a plus d'informations liées à celui-ci .

En particulier, un pointeur a un type. Sur la plupart des plates-formes, le type du pointeur n'a pas d'influence lors de l'exécution, mais il a une influence qui dépasse celle du type lors de la compilation. Si p est un pointeur sur int (int *p;), Alors p + 1 Pointe sur un entier qui est sizeof(int) octets après p (en supposant que p + 1 est toujours un pointeur valide). Si q est un pointeur sur char qui pointe vers la même adresse que p (char *q = p;), Alors q + 1 N'est pas la même. adresse comme p + 1. Si vous considérez le pointeur comme une adresse, 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 différentes représentations (différentes configurations de bits en mémoire) qui pointent vers le même emplacement en mémoire. Vous pouvez les considérer comme des pointeurs différents contenant la même adresse ou des adresses différentes pour le même emplacement - la métaphore n'est pas claire dans ce cas. L'opérateur == Vous indique toujours si les deux opérandes pointent vers le même emplacement. Par conséquent, vous pouvez avoir p == q Même si p et q avoir différents modèles de bits.

Il existe même des environnements dans lesquels les pointeurs transportent d'autres informations que l'adresse, telles que des informations de type ou d'autorisation. Vous pouvez facilement passer votre vie de programmeur sans rencontrer ces problèmes.

Il existe des environnements où différents types de pointeurs ont différentes représentations. Vous pouvez y voir différentes sortes d'adresses ayant différentes représentations. Par exemple, certaines architectures ont des pointeurs sur des octets et des pointeurs Word, ou des pointeurs sur des objets et des pointeurs sur des fonctions.

Dans l’ensemble, considérer les pointeurs comme des adresses n’est pas si grave tant que vous gardez à l’esprit que

  • il ne s'agit que de pointeurs valides et non nuls qui sont des adresses;
  • vous pouvez avoir plusieurs adresses pour le même emplacement;
  • vous ne pouvez pas faire d'arithmétique sur les adresses, et il n'y a pas d'ordre sur elles;
  • le pointeur porte également des informations de type.

Faire l'inverse est beaucoup 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 binaire pouvant être lu comme un entier, et vous pouvez dire que cet entier est une adresse. Mais dans l'autre sens, chaque entier n'est pas un pointeur.

Il y a d'abord des limitations bien connues; Par exemple, un entier désignant un emplacement situé 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 nécessitant un alignement; Par exemple, sur une plate-forme où int nécessite un alignement de 4 octets, 0x7654321 ne peut pas être une valeur int* valide.

Cependant, cela va bien au-delà, car lorsque vous placez un pointeur sur un entier, vous vous exposez à de nombreux problèmes. Une grande partie de ce problème tient au fait que l'optimisation des compilateurs est bien meilleure en microoptimisation que ce à quoi la plupart des programmeurs s'attendent, de sorte que leur modèle mental du fonctionnement d'un programme est profondément faux. Ce n'est pas parce que vous avez des pointeurs avec la même adresse qu'ils sont équivalents. Par exemple, considérons l'extrait suivant:

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

Vous pouvez vous attendre à ce que sur une machine ordinaire où sizeof(int)==4 et sizeof(short)==2, ceci imprime 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 s'en rendre compte. Puisque p a un type différent de &x, Changer le pointage de p ne peut pas affecter ce que &x Pointe (en dehors de quelques exceptions bien définies). Par conséquent, le compilateur est libre de conserver la valeur de x dans un registre et de ne pas le mettre à jour, car *p Change. Le programme déréférence deux pointeurs à la même adresse et obtient deux valeurs différentes!

La morale de cet exemple est qu'il est correct de penser à un pointeur (non valide null) en tant qu'adresse, tant que vous restez dans 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 à comprendre intuitivement si vous ne savez pas ce qui se passe sous le capot. Et ce qui se passe sous le capot, c’est que le lien entre les pointeurs et les adresses est quelque peu lâche, à la fois pour prendre en charge les architectures de processeurs "exotiques" et pour permettre l’optimisation des compilateurs.

Pensez donc que les pointeurs sont des adresses comme un premier pas dans votre compréhension, mais ne suivez pas cette intuition trop loin.

32
Gilles

Un pointeur est une variable qui conserve l'adresse mémoire, pas l'adresse elle-même. Cependant, vous pouvez déréférencer un pointeur et accéder à l'emplacement de la mémoire.

Par exemple:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

C'est ça. C'est si simple.

enter image description here

Un programme pour démontrer ce que je dis et son résultat est ici:

http://ideone.com/rcSUsb

Le programme:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}
19
Aniket Inge

Il est difficile de dire exactement ce que les auteurs de ces livres veulent dire exactement. Le fait qu'un pointeur contienne ou non une adresse dépend de la manière dont vous définissez une adresse et de la manière dont vous définissez un pointeur.

À en juger par toutes les réponses écrites, certaines personnes supposent que (1) une adresse doit être un entier et (2) un pointeur n'a pas besoin d'être virtuel pour ne pas être dit de la sorte dans la spécification. Avec ces hypothèses, il est clair que les pointeurs ne contiennent pas nécessairement d'adresses.

Cependant, nous voyons que tandis que (2) est probablement vrai, (1) n'a probablement pas à être vrai. Et que dire du fait que le & s'appelle l'opérateur adresse de selon la réponse de @ CornStalks? Cela signifie-t-il que les auteurs de la spécification souhaitent qu'un pointeur contienne une adresse?

Alors peut-on dire, le pointeur contient une adresse, mais une adresse ne doit pas nécessairement être un entier? Peut être.

Je pense que tout cela est du jibberish pédant un discours sémantique. Il est totalement inutile pratiquement parlant. Pouvez-vous penser à un compilateur qui génère du code de telle sorte que la valeur d'un pointeur ne soit pas une adresse? Si oui quoi? C'est ce que je pensais...

Je pense que l'auteur du livre (le premier extrait qui affirme que les pointeurs ne sont pas nécessairement des adresses) fait probablement référence au fait qu'un pointeur accompagne les informations de type inhérentes.

Par exemple,

 int x;
 int* y = &x;
 char* z = &x;

y et z sont des pointeurs, mais y + 1 et z + 1 sont différents. si ce sont des adresses de mémoire, ces expressions ne vous donneraient-elles pas la même valeur?

Et ici se trouve le penser aux pointeurs comme s’il s’agissait d’adresses mène généralement au chagrin. Des bugs ont été écrits parce que les gens pensent à propos des pointeurs comme s'il s'agissait d'adresses, et ceci conduit habituellement à un chagrin.

55555 n'est probablement pas un pointeur, bien qu'il puisse s'agir d'une adresse, mais (int *) 55555 est un pointeur. 55555 + 1 = 55556, mais (int *) 55555 + 1 est égal à 55559 (+/- différence en termes de taille de (int)).

16
thang

Eh bien, un pointeur est un abstraction représentant un emplacement de mémoire. Notez que la citation ne dit pas qu'il est faux de penser aux pointeurs comme s'il s'agissait d'adresses de mémoire, mais simplement que cela "conduit habituellement à un chagrin". En d'autres termes, cela vous conduit à avoir des attentes incorrectes.

La source la plus probable de chagrin est certainement arithmétique de pointeur,, qui est en fait l’un des points forts de C. Si un pointeur était une adresse, vous vous attendriez à ce que l'arithmétique de pointeur soit une arithmétique d'adresse; mais ce n'est pas. Par exemple, ajouter 10 à une adresse devrait vous donner une adresse plus grande de 10 unités d'adressage; mais ajouter 10 à un pointeur l'incrémente de 10 fois la taille du type d'objet vers lequel il pointe (et même pas la taille réelle, mais arrondie à une limite d'alignement). Avec un int * sur une architecture ordinaire avec des entiers 32 bits, l’ajout de 10 la incrémenterait de 40 unités d’adressage (octets). Les programmeurs C expérimentés en sont conscients et vivent avec, mais votre auteur n’est évidemment pas un adepte des métaphores bâclées.

Il y a la question supplémentaire de comment le contenu du pointeur représente l'emplacement de la mémoire: Comme beaucoup de réponses l'ont expliqué, un adresse n'est pas toujours un int (ou long). Dans certaines architectures, une adresse est un "segment" plus un décalage. Un pointeur peut même ne contenir que le décalage dans le segment en cours (pointeur "proche"), qui n'est pas en soi une adresse mémoire unique. Et le contenu du pointeur peut n’avoir qu’une relation indirecte avec une adresse de mémoire telle que le comprend le matériel. Mais l'auteur de la citation citée ne mentionne même pas la représentation. Je pense donc qu'il s'agissait d'une équivalence conceptuelle plutôt que d'une représentation.

14
alexis

Voici comment j'ai déjà expliqué cela à certaines personnes confuses: Un pointeur a deux attributs qui affectent son comportement. Il a une valeur, qui est (dans les environnements typiques) une adresse mémoire, et un type, qui vous indique le type et la taille de l'objet sur lequel il pointe.

Par exemple, étant donné:

union {
    int i;
    char c;
} u;

Vous pouvez avoir trois pointeurs différents pointant tous sur ce même objet:

void *v = &u;
int *i = &u.i;
char *c = &u.c;

Si vous comparez les valeurs de ces pointeurs, ils sont tous égaux:

v==i && i==c

Cependant, si vous incrémentez chaque pointeur, vous verrez que le type auquel ils pointent devient pertinent.

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

Les variables i et c auront des valeurs différentes à ce stade, car i++ fait en sorte que i contienne l’adresse du prochain entier accessible, et c++ fait en sorte que c pointe vers le caractère adressable suivant. Généralement, les entiers utilisent plus de mémoire que de caractères, donc i aura une valeur supérieure à celle de c après leur incrémentation.

12
Mark Bessey

Vous avez raison et raison. Normalement, un pointeur est juste une adresse, vous pouvez donc le transtyper en entier et effectuer des opérations arithmétiques.

Mais parfois, les pointeurs ne sont qu'une partie d'une adresse. Sur certaines architectures, un pointeur est converti en une adresse avec addition de base ou autre CPU le registre est utilisé.

Mais ces jours-ci, sur PC et sur ARM avec un modèle de mémoire à plat et un langage C compilé nativement, il est normal de penser qu'un pointeur est une adresse entière pour certains placer dans la RAM adressable unidimensionnelle.

8
exebook

Mark Bessey l'a déjà dit, mais il faut insister sur ce point avant de le comprendre.

Le pointeur a autant à voir avec une variable qu'un littéral 3.

Pointeur est un tuple d'une valeur (d'une adresse) et d'un type (avec des propriétés supplémentaires, telles que la lecture seule). Le type (et les éventuels paramètres supplémentaires) peut définir ou restreindre le contexte; par exemple. __far ptr, __near ptr: Quel est le contexte de l'adresse: pile, tas, adresse linéaire, décalage quelque part, mémoire physique ou quoi.

C'est la propriété de type qui rend l'arithmétique de pointeur légèrement différente de l'arithmétique entière.

Les exemples de compteur d'un pointeur de ne pas être une variable sont trop nombreux pour être ignorés

  • fopen retournant un pointeur FILE. (où est la variable)
  • le pointeur de pile ou le pointeur de cadre étant des registres généralement non adressables

    *(int *)0x1231330 = 13; - transforme une valeur entière arbitraire en un type pointer_of_integer et écrit/lit un entier sans même introduire de variable

Dans la vie d'un programme C, il y aura de nombreuses autres instances de pointeurs temporaires qui n'ont pas d'adresse - et ne sont donc pas des variables, mais des expressions/valeurs avec un type associé à la compilation.

8
Aki Suihkonen

Un pointeur, comme toute autre variable de C, est fondamentalement une collection de bits qui peuvent être représentés par une ou plusieurs valeurs unsigned char Concaténées (comme avec tout autre type de cariable, sizeof(some_variable) indiquera le nombre de valeurs unsigned char). Ce qui différencie un pointeur des autres variables est qu’un compilateur C interprète les bits d’un pointeur comme identifiant, d’une manière ou d’une autre, un endroit où une variable peut être stockée. En C, contrairement à d'autres langages, il est possible de demander de l'espace pour plusieurs variables, puis de convertir un pointeur en une valeur quelconque de cet ensemble en un pointeur en une autre variable de cet ensemble.

De nombreux compilateurs implémentent des pointeurs en utilisant leurs bits stockant les adresses réelles des ordinateurs, mais ce n'est pas la seule implémentation possible. Une implémentation peut conserver un tableau - non accessible au code utilisateur - répertoriant l'adresse matérielle et la taille allouée de tous les objets de mémoire (ensembles de variables) qu'un programme utilisait, et faire en sorte que chaque pointeur contienne un index dans un tableau. avec un décalage de cet index. Une telle conception permettrait à un système non seulement de restreindre le code à ne fonctionner que sur la mémoire dont il était propriétaire, mais également de garantir qu'un pointeur sur un élément de mémoire ne puisse pas être converti accidentellement en un pointeur sur un autre élément de mémoire (dans un système utilisant du matériel). les adresses, si foo et bar sont des tableaux de 10 éléments stockés consécutivement en mémoire, un pointeur sur l'élément "onzième" de foo pourrait pointer vers le premier élément de bar, mais dans un système où chaque "pointeur" est un ID d'objet et un offset, le système peut intercepter si le code tente d'indexer un pointeur sur foo au-delà de sa plage allouée). Un tel système pourrait également éliminer les problèmes de fragmentation de la mémoire, car les adresses physiques associées à n’importe quel pointeur pourraient être déplacées.

Notez que, bien que les pointeurs soient quelque peu abstraits, ils ne sont pas assez abstraits pour permettre à un compilateur C entièrement conforme aux normes d'implémenter un garbage collector. Le compilateur C spécifie que chaque variable, y compris les pointeurs, est représentée par une séquence de valeurs unsigned char. N'importe quelle variable, on peut la décomposer en une suite de nombres et ensuite reconvertir cette suite en une variable du type original. Par conséquent, il serait possible pour un programme de calloc stocker (en recevant un pointeur), y stocker quelque chose, décomposer le pointeur en une série d'octets, les afficher à l'écran, puis effacer toutes les références. pour eux. Si le programme acceptait alors certains numéros du clavier, les reconstituait en un pointeur, puis tentait de lire les données à partir de ce pointeur, et si l'utilisateur saisissait les mêmes chiffres que ceux précédemment affichés, le programme serait tenu de sortir les données. qui avaient été stockés dans la mémoire calloc 'ed. Puisqu'il n'y a aucun moyen concevable que l'ordinateur puisse savoir si l'utilisateur a fait une copie des numéros affichés, l'ordinateur ne pourrait pas savoir si la mémoire susmentionnée pourrait être utilisée à l'avenir.

7
supercat

Un pointeur est un type de variable disponible en mode natif en C/C++ et contenant une adresse de mémoire. Comme toute autre variable, elle a sa propre adresse et utilise de la mémoire (le montant est spécifique à la plate-forme).

Un des problèmes que vous verrez à la suite de la confusion consiste à essayer de changer le référent dans une fonction en passant simplement le pointeur par valeur. Cela créera une copie du pointeur dans la portée de la fonction et de toute modification apportée à l'endroit où ce nouveau pointeur "points" ne modifiera pas le référent du pointeur dans la portée qui a appelé la fonction. Afin de modifier le pointeur actuel dans une fonction, on devrait normalement passer un pointeur à un pointeur.

6
Matthew Sanders

BREF RESUME (que je mettrai aussi en haut):

(0) Considérer les pointeurs comme des adresses est souvent un bon outil d'apprentissage et constitue souvent l'implémentation réelle des pointeurs vers des types de données ordinaires.

(1) Mais sur beaucoup, peut-être la plupart des compilateurs, les pointeurs sur les fonctions ne sont pas des adresses, mais sont plus gros qu'une adresse (généralement 2x, parfois plus), ou sont en fait des pointeurs sur une structure en mémoire qui contient les adresses de fonction et d'autres choses comme une piscine constante.

(2) Les pointeurs vers les membres de données et les méthodes sont souvent même plus étranges.

(3) Code x86 hérité avec problèmes de pointeur FAR et NEAR

(4) Plusieurs exemples, notamment IBM AS/400, avec des "gros pointeurs" sécurisés.

Je suis sûr que vous pouvez trouver plus.

DÉTAIL:

UMMPPHHH !!!!! Jusqu'à présent, beaucoup de réponses sont des réponses assez typiques de "programmeur professionnel" - mais pas de compilateur professionnel ou matériel. Étant donné que je prétends être un logiciel, et que je travaille souvent avec des logiciels de compilation, permettez-moi de mentionner mes deux sous:

Sur beaucoup, probablement la plupart des compilateurs C, un pointeur sur des données de type T correspond en fait à l'adresse de T.

Bien.

Mais, même sur beaucoup de ces compilateurs, certains pointeurs ne sont pas des adresses. Vous pouvez le savoir en regardant sizeof(ThePointer).

Par exemple, les pointeurs sur les fonctions sont parfois beaucoup plus volumineux que les adresses ordinaires. Ou bien, ils peuvent impliquer un niveau d'indirection. Cet article fournit une description, impliquant le processeur Intel Itanium, mais j'en ai vu d'autres. En général, pour appeler une fonction, vous devez connaître non seulement l'adresse du code de la fonction, mais également l'adresse du pool constant de la fonction - une région de la mémoire à partir de laquelle les constantes sont chargées avec une seule instruction de chargement, sans que le compilateur ait à générer une constante de 64 bits parmi plusieurs instructions Load Immediate, Shift et OR. Ainsi, au lieu d'une seule adresse 64 bits, vous avez besoin de 2 adresses de 64 bits. Certaines ABI (interfaces binaires d'application) le déplacent. environ 128 bits, alors que d’autres utilisent un niveau d’indirection, le pointeur de fonction étant en fait l’adresse d’un descripteur de fonction contenant les 2 adresses réelles mentionnées ci-dessus. Laquelle est la meilleure? Cela dépend de votre point de vue: performance, taille du code, et certains problèmes de compatibilité - le code suppose souvent qu'un pointeur peut être converti en un long ou un long long, mais peut également supposer que le long long correspond exactement à 64 bits. Ce code peut ne pas être conforme aux normes, mais les clients peuvent néanmoins le souhaiter. travail.

Beaucoup d’entre nous ont des souvenirs douloureux de la vieille architecture segmentée Intel x86, avec NEAR POINTERs et FAR POINTERS. Heureusement, elles sont presque éteintes à ce jour. Un simple résumé: en mode réel 16 bits, l’adresse linéaire réelle était

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

Alors qu'en mode protégé, il pourrait être

LinearAddress = SegmentRegister[SegNum].base + offset

l'adresse résultante étant vérifiée par rapport à une limite définie dans le segment. Certains programmes n'utilisaient pas vraiment les déclarations de pointeur CAR/F ++ et NEAR standard, mais beaucoup venaient de dire *T --- mais il y avait des commutateurs de compilateur et de lieur, ainsi, par exemple, les pointeurs de code pouvaient être des pointeurs proches, juste un décalage de 32 bits par rapport à tout ce qui se trouvait dans le registre CS (Code Segment), alors que les pointeurs de données pouvaient être des pointeurs FAR, spécifiant à la fois un numéro de segment de 16 bits et un décalage de 32 bits pour une valeur de 48 bits. Maintenant, ces deux quantités sont certainement liées à l'adresse, mais comme elles ne sont pas de la même taille, quelle est l'adresse? De plus, les segments comportaient également des autorisations - lecture seule, lecture-écriture, exécutable - en plus des éléments liés à l'adresse réelle.

Un exemple plus intéressant, IMHO, est (ou était peut-être) la famille IBM AS/400. Cet ordinateur a été l'un des premiers à implémenter un système d'exploitation en C++. Les pointeurs sur ce machime étaient généralement 2X la taille réelle de l’adresse - par exemple. comme cette présentation dit, pointeurs 128 bits, mais les adresses réelles étaient 48-64 bits, et, encore une fois, quelques informations supplémentaires, ce qui s'appelle une capacité, qui fournissait des autorisations telles que lire, écrire, ainsi qu'une limite pour empêcher le débordement de tampon. Oui, vous pouvez le faire de manière compatible avec le C/C++ - et si cela était omniprésent, le PLA chinois et la mafia slave ne pourraient pas pirater autant de systèmes informatiques occidentaux. Mais historiquement, la plupart des programmes C/C++ ont négligé la sécurité pour la performance. Le plus intéressant est que la famille AS400 a permis au système d’exploitation de créer des pointeurs sécurisés, qui pourraient être affectés à du code non privilégié, mais que le code non privilégié ne pourrait ni falsifier ni altérer. Encore une fois, la sécurité et bien que conforme aux normes, une grande partie du code C/C++ non conforme aux normes ne fonctionnerait pas dans un tel système sécurisé. Encore une fois, il y a des normes officielles et il y a des normes de facto.

Maintenant, je vais sortir de ma tribune de sécurité et mentionner quelques autres façons dont les pointeurs (de types différents) ne sont souvent pas des adresses: pointeurs vers les membres de données, méthodes de pointeurs vers les fonctions de membre, et leurs versions statiques sont plus grandes qu'un adresse ordinaire. Comme ce post dit:

Il existe de nombreuses façons de résoudre ce problème [problèmes liés à un héritage simple ou multiple et à l'héritage virtuel]. Voici comment le compilateur Visual Studio décide de le gérer: Un pointeur sur une fonction membre d'une classe à plusieurs héritages est en réalité une structure.

Comme vous pouvez probablement le deviner de mon pontificat sur la sécurité (en), j'ai été impliqué dans des projets matériel/logiciel C/C++ dans lesquels un pointeur était traité davantage comme une capacité qu'une adresse brute.

Je pourrais continuer, mais j'espère que vous avez l'idée.

BREF RESUME (que je mettrai aussi en haut):

(0) Considérer les pointeurs comme des adresses est souvent un bon outil d'apprentissage et constitue souvent la mise en œuvre réelle des pointeurs vers des types de données ordinaires.

(1) Mais sur beaucoup, peut-être la plupart des compilateurs, les pointeurs sur des fonctions ne sont pas des adresses, mais sont plus gros qu'une adresse (généralement 2X, parfois plus), ou sont en fait des pointeurs sur une structure en mémoire qui contient les adresses de fonction et d'autres choses comme une piscine constante.

(2) Les pointeurs vers les membres de données et les méthodes sont souvent même plus étranges.

(3) Code x86 hérité avec problèmes de pointeur FAR et NEAR

(4) Plusieurs exemples, notamment IBM AS/400, avec des "gros pointeurs" sécurisés.

Je suis sûr que vous pouvez trouver plus.

6
Krazy Glew

Un pointeur est simplement une autre variable utilisée pour contenir l'adresse d'un emplacement mémoire (généralement l'adresse mémoire d'une autre variable).

4
Tuxdude

Vous pouvez le voir de cette façon. Un pointeur est une valeur qui représente une adresse dans l'espace mémoire adressable.

4
Valentin Radu

Avant de comprendre les pointeurs, nous devons comprendre les objets. Les objets sont des entités qui existent et ont un spécificateur d'emplacement appelé une adresse. Un pointeur est simplement une variable comme n'importe quelle autre variable de C avec un type appelé pointer dont le contenu est interprété comme l'adresse d'un objet prenant en charge l'opération suivante.

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

Un pointeur est classé en fonction du type d'objet auquel il fait actuellement référence. La seule partie de l'information qui compte est la taille de l'objet.

Tout objet supporte une opération, & (adresse de), qui récupère le spécificateur d'emplacement (adresse) de l'objet en tant que type d'objet de pointeur. Cela devrait dissiper la confusion entourant la nomenclature car cela aurait du sens d'appeler & en tant qu'opération d'un objet plutôt que d'un pointeur dont le type résultant est un pointeur du type d'objet.

Note Tout au long de cette explication, j'ai laissé de côté le concept de mémoire.

3
Abhijit

Un pointeur est simplement une autre variable pouvant contenir une adresse mémoire, généralement celle d’une autre variable. Un pointeur étant une variable, il a aussi une adresse mémoire.

3
Xavier DSouza

Une adresse est utilisée pour identifier un morceau de stockage de taille fixe, généralement pour chaque octet, sous forme d'entier. Ceci est précisément appelé adresse octet , qui est également utilisée par l'ISO C. Il peut y avoir d'autres méthodes pour construire une adresse, par exemple. pour chaque bit. Cependant, seule l'adresse d'octet est si souvent utilisée, nous omettons généralement "octet".

Techniquement, une adresse n'est jamais une valeur en C, car la définition du terme "valeur" dans (ISO) C est:

signification précise du contenu d'un objet lorsqu'il est interprété comme ayant type spécifique

(Souligné par moi.) Cependant, il n'y a pas un tel "type d'adresse" en C.

Le pointeur n'est pas le même. Le pointeur est une sorte de type en langage C. Il existe plusieurs types de pointeurs distincts. Ils n'obéissent pas nécessairement à un ensemble identique de règles du langage, par exemple. l'effet de ++ sur une valeur de type int* contre. char*.

Une valeur en C peut être de type pointeur. C'est ce qu'on appelle une valeur de pointeur . Pour être clair, une valeur de pointeur n'est pas un pointeur en langage C. Mais nous sommes habitués à les mélanger car, en C, cela n’est pas susceptible d’être ambigu: si nous appelons une expression p comme "pointeur", il s’agit simplement d’une valeur de pointeur, mais pas d’un type, le type nommé en C n'est pas exprimé par une expression , mais par un nom de type ou un typedef-name .

Certaines autres choses sont subtiles. En tant qu'utilisateur C, tout d'abord, il faut savoir ce que object signifie:

région de stockage de données dans l'environnement d'exécution, dont le contenu peut représenter des valeurs

Un objet est une entité pour représenter des valeurs, qui sont d'un type spécifique. Un pointeur est un type d'objet . Donc, si nous déclarons int* p;, alors p signifie "un objet de type pointeur" ou un "objet pointeur".

Notez qu'il existe no "variable" définie normativement par la norme (en fait, elle n'est jamais utilisée comme nom par ISO C dans un texte normatif). Cependant, de manière informelle, nous appelons un objet une variable, comme le fait un autre langage. (Mais toujours pas si exactement, par exemple, en C++, une variable peut être de type référence , ce qui n'est pas un objet.) Les phrases "objet pointeur" ou "variable de pointeur" sont parfois traités comme "valeur de pointeur" comme ci-dessus, avec une légère différence probable. (Un autre ensemble d’exemples est "array".)

Puisque le pointeur est un type et que l'adresse est en fait "sans type" en C, une valeur de pointeur "contient" une adresse. Et une expression de type pointeur peut donner une adresse, par ex.

ISO C11 6.5.2.

3 Le unaire & opérateur renvoie l'adresse de son opérande.

Notez que cette formulation est introduite par WG14/N1256, c’est-à-dire ISO C99: TC3. En C99 il y a

3 Le unaire & l'opérateur renvoie l'adresse de son opérande.

Cela reflète l'opinion du comité: une adresse est pas une valeur de pointeur renvoyée par le unaire & opérateur.

Malgré le libellé ci-dessus, il y a encore quelques dégâts, même dans les normes.

ISO C11 6.6

9 Une constante d'adresse est un pointeur nul, un pointeur sur une lvalue désignant un objet de durée de stockage statique ou un pointeur sur un indicateur de fonction.

ISO C++ 11 5.19

3 ... Une expression constante d'adresse est une expression constante constante de type valeur de pointeur qui correspond à l'adresse d'un objet avec une durée de stockage statique, au adresse d'une fonction, ou à une valeur de pointeur nulle, ou à une valeur constante constante constante de type std::nullptr_t. ...

(Le brouillon standard C++ récent utilise un autre libellé, il n’ya donc pas ce problème.)

En réalité, les deux expressions "adresse constante" en C et "expression constante d'adresse" en C++ sont des expressions constantes des types de pointeur (ou au moins des types "de type pointeur" depuis C++ 11).

Et le unin intégré & opérateur est appelé "adresse-de" en C et C++; de même, std::addressof est introduit en C++ 11.

Ces dénominations peuvent amener une idée fausse. L'expression résultante est du type pointeur, donc elle serait interprétée comme suit: le résultat contient/donne une adresse, plutôt que est une adresse.

3
FrankHB

Un pointeur C est très similaire à une adresse de mémoire, mais avec des détails dépendants de la machine, ainsi que certaines caractéristiques non trouvées dans le jeu d’instructions de niveau inférieur.

Par exemple, un pointeur C est relativement richement typé. Si vous incrémentez un pointeur dans un tableau de structures, il passe facilement d'une structure à l'autre.

Les pointeurs sont soumis aux règles de conversion et fournissent une vérification du type de temps de compilation.

Il existe une valeur spéciale "pointeur nul" qui est portable au niveau du code source, mais dont la représentation peut différer. Si vous affectez une constante entière dont la valeur est zéro à un pointeur, celui-ci prend la valeur null du pointeur. Idem si vous initialisez un pointeur de cette façon.

Un pointeur peut être utilisé en tant que variable booléenne: il teste true s'il est différent de null et false s'il est null.

Dans un langage machine, si le pointeur null est une adresse amusante comme 0xFFFFFFFF, vous devrez peut-être effectuer des tests explicites pour cette valeur. C te le cache. Même si le pointeur null est 0xFFFFFFFF, vous pouvez le tester à l'aide de if (ptr != 0) { /* not null! */}.

Les utilisations de pointeurs qui subvertissent le système de types entraînent un comportement indéfini, alors qu'un code similaire en langage machine peut être bien défini. Les assembleurs assemblent les instructions que vous avez écrites, mais les compilateurs C optimisent en supposant que vous n'avez rien fait de mal. Si un float *p Le pointeur pointe vers un long n variable, et *p = 0.0 est exécuté, le compilateur n’est pas obligé de gérer cela. Une utilisation ultérieure de n ne lira pas nécessairement le modèle de bits de la valeur float, mais ce sera peut-être un accès optimisé basé sur l'hypothèse du "aliasing strict" selon lequel n n'a pas été touché! En d’autres termes, l’hypothèse que le programme se comporte bien et que p ne devrait pas pointer sur n.

En C, les pointeurs vers le code et les pointeurs vers les données sont différents, mais sur de nombreuses architectures, les adresses sont les mêmes. Des compilateurs C peuvent être développés avec des pointeurs "gras", même si l’architecture cible n’en dispose pas. Les gros pointeurs signifient que les pointeurs ne sont pas simplement des adresses d'ordinateur, mais contiennent d'autres informations, telles que des informations sur la taille de l'objet pointé, pour la vérification des limites. Les programmes écrits de manière portable seront facilement compatibles avec ces compilateurs.

Comme vous pouvez le constater, il existe de nombreuses différences sémantiques entre les adresses de machine et les pointeurs C.

3
Kaz

Il dit "parce que cela confond ceux qui ne savent pas en quoi consistent les adresses" - c'est également vrai: si vous apprenez en quoi consistent les adresses, vous ne serez pas confus. Théoriquement, le pointeur est une variable pointant sur une autre, qui contient pratiquement une adresse, qui est l'adresse de la variable sur laquelle il pointe. Je ne sais pas pourquoi devrait masquer ce fait, ce n'est pas une science de fusée. Si vous comprenez les indicateurs, vous allez faire un pas en avant pour comprendre le fonctionnement des ordinateurs. Aller de l'avant!

2
ern0

En y réfléchissant, je pense que c'est une question de sémantique. Je ne pense pas que l'auteur ait raison, car la norme C fait référence à un pointeur comme tenant une adresse vers l'objet référencé, comme d'autres l'ont déjà mentionné ici. Cependant, adresse! = Adresse mémoire. Une adresse peut être vraiment n'importe quoi selon le standard C bien que cela conduise finalement à une adresse mémoire, le pointeur lui-même peut être un identifiant, un décalage + sélecteur (x86), vraiment n'importe quoi pourvu qu'il puisse décrire (après avoir mappé) - toute adresse mémoire dans l'espace adressable.

2
Valentin Radu

Une autre manière pour laquelle un pointeur C ou C++ diffère d'une simple adresse mémoire en raison des différents types de pointeurs que je n'ai pas vus dans les autres réponses (bien que, vu leur taille totale, je l'aurais peut-être oublié). Mais c’est probablement le plus important, car même les programmeurs expérimentés en C/C++ peuvent trébucher:

Le compilateur peut supposer que les pointeurs de types incompatibles ne pointent pas sur la même adresse, même s'ils le font clairement, ce qui peut donner un comportement qui ne serait pas possible avec un modèle de pointeur simple ==. Considérons le code suivant (en supposant que sizeof(int) = 2*sizeof(short)):

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

Notez qu'il existe une exception pour char*, donc manipuler des valeurs en utilisant char* est possible (bien que pas très portable).

1
celtschk

Disons simplement que les pointeurs sont en réalité une partie décalée du mécanisme de segmentation qui se traduit en adresse linéaire après la segmentation, puis en adresse physique après la pagination. Les adresses physiques sont réellement adressées par votre RAM.

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
0
router

Résumé rapide: une adresse C est une valeur, généralement représentée sous la forme d'une adresse de mémoire au niveau de l'ordinateur, avec un type spécifique.

Le mot "pointeur" non qualifié est ambigu. C a pointeur objets (variables), pointeur types , pointeur expressions et pointeur valeurs .

Il est très courant d'utiliser le mot "pointeur" pour signifier "objet pointeur", ce qui peut prêter à confusion - c'est pourquoi j'essaie d'utiliser "pointeur" comme adjectif plutôt que comme nom.

La norme C, du moins dans certains cas, utilise le mot "pointeur" pour signifier "valeur du pointeur". Par exemple, la description de malloc indique qu'elle "renvoie soit un pointeur nul, soit un pointeur sur l'espace alloué".

Alors, quelle est une adresse en C? C'est une valeur de pointeur, c'est-à-dire une valeur d'un type de pointeur particulier. (Sauf que la valeur d'un pointeur nul n'est pas nécessairement appelée "adresse", car ce n'est l'adresse de rien).

Description du standard du unaire & l'opérateur dit qu'il "donne l'adresse de son opérande". En dehors de la norme C, le mot "adresse" est couramment utilisé pour désigner une adresse de mémoire (physique ou virtuelle), généralement de la taille d'un mot (quel que soit le "mot" figurant sur un système donné).

Une "adresse" C est généralement implémentée en tant qu'adresse d'ordinateur, tout comme une valeur C int est généralement implémentée en tant que mot d'ordinateur. Mais une adresse C (valeur du pointeur) est plus qu'une adresse d'ordinateur. C'est une valeur typiquement représentée en tant qu'adresse machine, et c'est une valeur avec un type spécifique .

0
Keith Thompson

Une valeur de pointeur est une adresse. Une variable de pointeur est un objet pouvant stocker une adresse. Cela est vrai parce que c'est ce que la norme définit comme étant un pointeur. Il est important de le dire aux novices parce que les novices ignorent souvent la différence entre un pointeur et ce qu’il indique (c’est-à-dire qu’ils ne connaissent pas la différence entre une enveloppe et un bâtiment). La notion d'adresse (chaque objet a une adresse et c'est ce que stocke un pointeur) est importante car elle la trie.

Cependant, la norme parle à un niveau d'abstraction particulier. Les personnes dont l'auteur dit qui "savent de quoi parlent les adresses", mais qui sont nouvelles pour C, doivent nécessairement avoir appris à connaître les adresses à un niveau d'abstraction différent - peut-être en programmant le langage Assembly. Il n'y a aucune garantie que l'implémentation C utilise la même représentation pour les adresses que celle utilisée par les opcodes des CPU (désignée par "l'adresse du magasin" dans ce passage), que ces personnes connaissent déjà.

Il parle ensuite de "manipulation d'adresse parfaitement raisonnable". En ce qui concerne la norme C, il n’existe fondamentalement pas de "manipulation d’adresses parfaitement raisonnable". L'addition est définie sur des pointeurs et c'est essentiellement cela. Bien sûr, vous pouvez convertir un pointeur en entier, effectuer des opérations au niveau des bits ou arithmétiques, puis le reconvertir. Cela n’est pas garanti par la norme, aussi, avant d’écrire ce code, vous feriez mieux de savoir comment votre implémentation C particulière représente les pointeurs et effectue cette conversion. Il utilise probablement la représentation d'adresse que vous attendez, mais ce n'est pas votre faute parce que vous n'avez pas lu le manuel. Ce n'est pas une confusion, c'est une procédure de programmation incorrecte ;-)

En bref, C utilise un concept d'adresse plus abstrait que l'auteur.

Le concept de l'auteur d'une adresse bien sûr n'est pas non plus le mot le plus bas en la matière. Qu'en est-il des mappages de mémoire virtuelle et de l'adressage physique RAM sur plusieurs puces, le nombre que vous indiquez au processeur est "l'adresse du magasin" auquel vous souhaitez accéder n'a en principe rien à voir avec l'emplacement des données souhaitées se trouve dans le matériel. Il s’agit de couches d’indirection et de représentation, mais l’auteur en a choisi une pour les privilèges. Si vous envisagez de le faire lorsque vous parlez de C, choisissez le niveau C privilège !

Personnellement, je ne pense pas que les remarques de l'auteur soient si utiles, sauf dans le contexte de l'introduction de C aux programmeurs d'assemblage. Ce n'est certainement pas utile pour ceux qui viennent de langages de niveau supérieur de dire que les valeurs de pointeur ne sont pas des adresses. Il serait bien préférable de reconnaître la complexité que de dire que la CPU a le monopole de dire ce qu’est une adresse et donc que les valeurs du pointeur C "ne sont pas" des adresses. Ce sont des adresses, mais elles peuvent être écrites dans une langue différente de celle qu’il désigne. Distinguer les deux choses dans le contexte de C en "adresse" et "adresse en magasin" serait approprié, à mon avis.

0
Steve Jessop