Que signifie «déréférencer» un pointeur?

Veuillez inclure un exemple avec l’explication.

Revoir la terminologie de base

Il est généralement suffisant – sauf si vous programmez l’assemblage – d’envisager un pointeur contenant une adresse mémoire numérique, 1 correspondant au deuxième octet de la mémoire du processus, 2 le troisième, 3 le quasortingème, etc.

  • Qu’est-il arrivé à 0 et au premier octet? Eh bien, nous y reviendrons plus tard – voir les pointeurs nuls ci-dessous.
  • Pour une définition plus précise de ce que les pointeurs stockent et de la manière dont la mémoire et les adresses se rapportent, voir “Plus d’informations sur les adresses de mémoire et pourquoi vous n’avez probablement pas besoin de savoir” .

Lorsque vous souhaitez accéder aux données / valeurs dans la mémoire vers laquelle pointe le pointeur – le contenu de l’adresse avec cet index numérique – vous devez alors déréférencer le pointeur.

Différents langages informatiques ont des notations différentes pour indiquer au compilateur ou à l’interpréteur que vous vous intéressez maintenant à la valeur pointée – je me concentre sur C et C ++.

Un scénario de pointeur

Considérez en C, étant donné un pointeur tel que p ci-dessous …

 const char* p = "abc"; 

… quatre octets avec les valeurs numériques utilisées pour encoder les lettres ‘a’, ‘b’, ‘c’ et un octet 0 pour indiquer la fin des données textuelles, sont stockés quelque part dans la mémoire et l’adresse numérique de les données sont stockées dans p .

Par exemple, si le littéral de chaîne se trouvait à l’adresse 0x1000 et p un pointeur 32 bits à 0x2000, le contenu de la mémoire serait le suivant:

 Memory Address (hex) Variable name Contents 1000 'a' == 97 (ASCII) 1001 'b' == 98 1002 'c' == 99 1003 0 ... 2000-2003 p 1000 hex 

Notez qu’il n’y a pas de nom de variable / identifiant pour l’adresse 0x1000, mais nous pouvons indirectement faire référence à la chaîne littérale en utilisant un pointeur stockant son adresse: p .

Déréférencer le pointeur

Pour faire référence aux caractères p auxquels on se réfère, on utilise p une de ces notations (encore une fois pour C):

 assert(*p == 'a'); // The first character at address p will be 'a' assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding // p and 1 times the size of the things to which p points: // In this case they're char which are 1 byte in C... assert(*(p + 1) == 'b'); // Another notation for p[1] 

Vous pouvez également déplacer des pointeurs à travers les données pointées, en les déréférant au fur et à mesure:

 ++p; // Increment p so it's now 0x1001 assert(*p == 'b'); // p == 0x1001 which is where the 'b' is... 

Si vous avez des données sur lesquelles écrire, vous pouvez faire des choses comme ceci:

 int x = 2; int* p_x = &x; // Put the address of the x variable into the pointer p_x *p_x = 4; // Change the memory at the address in p_x to be 4 assert(x == 4); // Check x is now 4 

Au-dessus, vous devez savoir au moment de la compilation que vous auriez besoin d’une variable appelée x , et le code demande au compilateur de déterminer où il doit être stocké, en veillant à ce que l’adresse soit disponible via &x .

Déréférencement et access à un membre de données de structure

Dans C, si vous avez une variable qui est un pointeur sur une structure avec des membres de données, vous pouvez accéder à ces membres à l’aide de l’opérateur de déréférencement -> :

 typedef struct X { int i_; double d_; } X; X x; X* p = &x; p->d_ = 3.14159; // Dereference and access data member x.d_ (*p).d_ *= -1; // Another equivalent notation for accessing x.d_ 

Types de données multi-octets

Pour utiliser un pointeur, un programme informatique doit également connaître le type de données pointé. Si ce type de données nécessite plus d’un octet, le pointeur pointe normalement vers l’octet de numéro le plus bas dans les données.

Donc, en regardant un exemple légèrement plus complexe:

 double sizes[] = { 10.3, 13.4, 11.2, 19.4 }; double* p = sizes; assert(p[0] == 10.3); // Knows to look at all the bytes in the first double value assert(p[1] == 13.4); // Actually looks at bytes from address p + 1 * sizeof(double) // (sizeof(double) is almost always eight bytes) assert(++p); // Advance p by sizeof(double) assert(*p == 13.4); // The double at memory beginning at address p has value 13.4 *(p + 2) = 29.8; // Change sizes[3] from 19.4 to 29.8 // Note: earlier ++p and + 2 here => sizes[3] 

Pointeurs vers la mémoire allouée dynamicment

Parfois, vous ne savez pas combien de mémoire vous aurez besoin jusqu’à ce que votre programme soit en cours d’exécution et voit quelles données sont lancées dessus … alors vous pouvez allouer dynamicment de la mémoire en utilisant malloc . Il est courant de stocker l’adresse dans un pointeur …

 int* p = malloc(sizeof(int)); // Get some memory somewhere... *p = 10; // Dereference the pointer to the memory, then write a value in fn(*p); // Call a function, passing it the value at address p (*p) += 3; // Change the value, adding 3 to it free(p); // Release the memory back to the heap allocation library 

En C ++, l’allocation de mémoire se fait normalement avec le new opérateur et la désallocation avec delete :

 int* p = new int(10); // Memory for one int with initial value 10 delete p; p = new int[10]; // Memory for ten ints with unspecified initial value delete[] p; p = new int[10](); // Memory for ten ints that are value initialised (to 0) delete[] p; 

Voir également les pointeurs intelligents C ++ ci-dessous.

Perdre et perdre des adresses

Un pointeur peut souvent être la seule indication de l’emplacement de certaines données ou d’un tampon en mémoire. Si une utilisation continue de ces données / tampons est nécessaire, ou la possibilité d’appeler free() ou de delete pour éviter de fuir la mémoire, le programmeur doit alors opérer sur une copie du pointeur …

 const char* p = asprintf("name: %s", name); // Common but non-Standard printf-on-heap // Replace non-printable characters with underscores.... for (const char* q = p; *q; ++q) if (!isprint(*q)) *q = '_'; printf("%s\n", p); // Only q was modified free(p); 

… ou orchestrez soigneusement l’inversion de tout changement …

 const size_t n = ...; p += n; ... p -= n; // Restore earlier value... 

C ++ pointeurs intelligents

En C ++, il est recommandé d’utiliser des objects pointeurs intelligents pour stocker et gérer les pointeurs, en les libérant automatiquement lorsque les destructeurs des pointeurs intelligents sont exécutés. Depuis C ++ 11, la bibliothèque standard en fournit deux, unique_ptr , lorsqu’il y a un seul propriétaire pour un object alloué …

 { std::unique_ptr p{new T(42, "meaning")}; call_a_function(p); // The function above might throw, so delete here is unreliable, but... } // p's destructor's guaranteed to run "here", calling delete 

… et shared_ptr pour la propriété partagée (en utilisant le comptage de référence ) …

 { std::shared_ptr p(new T(3.14, "pi")); number_storage.may_add(p); // Might copy p into its container } // p's destructor will only delete the T if number_storage didn't copy 

Pointeurs nuls

En C, NULL et 0 – et en plus en C ++ nullptr – peuvent être utilisés pour indiquer qu’un pointeur ne contient pas actuellement l’adresse mémoire d’une variable et ne doit pas être déréférencé ou utilisé dans l’arithmétique de pointeur. Par exemple:

 const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++ char c; while ((c = getopt(argc, argv, "f:")) != EOF) switch (c) { case f: p_filename = optarg; break; } if (p_filename) // Only NULL converts to false ... // Only get here if -f flag specified 

En C et C ++, tout comme les types numériques incorporés ne sont pas nécessairement bools par défaut sur 0 , ni bools sur false , les pointeurs ne sont pas toujours définis sur NULL . Toutes ces variables sont définies sur 0 / false / NULL quand il s’agit de variables static ou de variables membres directes ou indirectes (C ++ uniquement) d’objects statiques ou de leurs bases, ou encore d’initialisation nulle (par exemple new T(); et new T(x, y, z); effectuer une initialisation nulle sur les membres de T, y compris les pointeurs, tandis que les new T; ne le font pas.

De plus, lorsque vous affectez 0 , NULL et nullptr à un pointeur, les bits du pointeur ne sont pas nécessairement tous réinitialisés: le pointeur peut ne pas contenir “0” au niveau matériel ou se référer à l’adresse 0 dans votre espace d’adressage virtuel. Le compilateur est autorisé à stocker autre chose s’il a raison, mais peu importe ce qu’il fait – si vous comparez le pointeur à 0 , NULL , nullptr ou un autre pointeur auquel l’un de ceux-ci a été atsortingbué, la comparaison doit fonctionner comme prévu . Donc, sous le code source au niveau du compilateur, “NULL” est potentiellement un peu “magique” dans les langages C et C ++ …

En savoir plus sur les adresses mémoire et pourquoi vous n’avez probablement pas besoin de savoir

Plus ssortingctement, les pointeurs initialisés stockent un modèle de bit identifiant NULL ou une adresse mémoire (souvent virtuelle ).

Le cas simple est celui où il s’agit d’un décalage numérique dans tout l’espace d’adressage virtuel du processus; dans des cas plus complexes, le pointeur peut être relatif à une zone de mémoire spécifique, que l’UC peut sélectionner en fonction de registres “segment” de CPU ou d’une sorte d’identifiant de segment codé dans le modèle de bit et / ou instructions de code machine utilisant l’adresse.

Par exemple, un int* correctement initialisé pour pointer vers une variable int pourrait – après avoir été jeté dans un float* – accéder à une valeur de la mémoire “GPU” distincte de la variable int , puis une fois converti en fonction en tenant les opcodes de la machine pour la fonction.

Les langages de programmation 3GL tels que C et C ++ ont tendance à masquer cette complexité, telle que:

  • Si le compilateur vous donne un pointeur sur une variable ou une fonction, vous pouvez le déréférencer librement (tant que la variable n’est pas détruite / désallouée entre-temps) et que le compilateur doit par exemple restaurer un registre particulier instruction de code machine utilisée

  • Si vous placez un pointeur sur un élément dans un tableau, vous pouvez utiliser l’arithmétique du pointeur pour vous déplacer ailleurs dans le tableau, ou même former une adresse un peu plus loin que celle des autres pointeurs. dans le tableau (ou qui ont été déplacés de la même manière par l’arithmétique du pointeur vers la même valeur un-un-le-fin); encore une fois en C et C ++, c’est au compilateur de s’assurer que cela “fonctionne”

  • Des fonctions spécifiques du système d’exploitation, telles que le mappage de la mémoire partagée, peuvent vous donner des indications, et elles «fonctionneront» simplement dans la plage des adresses qui leur conviennent.

  • Les tentatives visant à déplacer des pointeurs légaux au-delà de ces limites ou à placer des nombres arbitraires sur des pointeurs ou des pointeurs sur des types non liés ont généralement un comportement indéfini. .Il peut être nécessaire de s’appuyer sur un comportement indéfini par C ou C ++, mais qui est néanmoins bien défini par leur matériel spécifique.

Déréférencer un pointeur signifie obtenir la valeur stockée dans l’emplacement de la mémoire pointée par le pointeur. L’opérateur * est utilisé pour cela, et s’appelle l’opérateur de déréférencement.

 int a = 10; int* ptr = &a; printf("%d", *ptr); // With *ptr I'm dereferencing the pointer. // Which means, I am asking the value pointed at by the pointer. // ptr is pointing to the location in memory of the variable a. // In a's location, we have 10. So, dereferencing gives this value. // Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a. *ptr = 20; // Now a's content is no longer 10, and has been modified to 20. 

Un pointeur est une “référence” à une valeur. Tout comme un numéro d’appel de bibliothèque est une référence à un livre. “Déréférencer” le numéro d’appel passe physiquement et récupère ce livre.

 int a=4 ; int *pA = &a ; printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ; // The * causes pA to DEREFERENCE... `a` via "callnumber" `pA`. printf( "%d\n", *pA ) ; // prints 4.. 

Si le livre n’est pas là, le bibliothécaire commence à crier, ferme la bibliothèque et deux personnes sont prêtes à enquêter sur la cause d’une personne qui va trouver un livre qui n’est pas là.

En termes simples, le déréférencement consiste à accéder à la valeur à partir d’un certain emplacement mémoire sur lequel pointe ce pointeur.

Code et explication de Pointer Basics :

L’opération de déréférencement commence au pointeur et suit sa flèche pour accéder à sa pointe. Le but peut être de regarder l’état de pointe ou de changer l’état de pointe. L’opération de déréférencement sur un pointeur ne fonctionne que si le pointeur a une pointe – le pointe doit être alloué et le pointeur doit être défini pour le pointer. L’erreur la plus courante dans le code du pointeur est d’oublier de configurer la pointe. Le crash d’exécution le plus courant en raison de cette erreur dans le code est une opération de déréférencement ayant échoué. En Java, le déréférencement incorrect sera signalé poliment par le système d’exécution. Dans les langages compilés tels que C, C ++ et Pascal, les déréférencements incorrects sont parfois bloqués, et d’autres fois, ils altèrent la mémoire de manière subtile et aléatoire. Les bugs de pointage dans les langages compilés peuvent être difficiles à suivre pour cette raison.

 void main() { int* x; // Allocate the pointer x x = malloc(sizeof(int)); // Allocate an int pointee, // and set x to point to it *x = 42; // Dereference x to store 42 in its pointee } 

Je pense que toutes les réponses précédentes sont fausses, car elles stipulent que le déréférencement signifie accéder à la valeur réelle. Wikipedia donne la définition correcte à la place: https://en.wikipedia.org/wiki/Dereference_operator

Il opère sur une variable de pointeur et renvoie une valeur l équivalente à la valeur à l’adresse du pointeur. Cela s’appelle “déréférencer” le pointeur.

Cela dit, nous pouvons déréférencer le pointeur sans jamais accéder à la valeur indiquée. Par exemple:

 char *p = NULL; *p; 

Nous avons déréférencé le pointeur NULL sans accéder à sa valeur. Ou nous pourrions faire:

 p1 = &(*p); sz = sizeof(*p); 

Encore une fois, déréférencer, mais ne jamais accéder à la valeur. Un tel code ne tombera PAS en panne: la panne survient lorsque vous accédez réellement aux données par un pointeur non valide. Malheureusement, selon la norme, le déréférencement d’un pointeur non valide est un comportement indéfini (à quelques exceptions près), même si vous n’essayez pas de toucher les données réelles.

Donc, bref: déréférencer le pointeur signifie lui appliquer l’opérateur de déréférencement. Cet opérateur renvoie simplement une valeur l pour votre utilisation future.