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

541

Veuillez inclure un exemple avec l'explication.

asir
la source
cela peut vous aider: stackoverflow.com/questions/2795575/…
Harry Joy
24
int *p;définirait un pointeur sur un entier et *pdéréférencerait ce pointeur, ce qui signifie qu'il récupérerait en fait les données vers lesquelles p pointe.
Peyman le
4
Binky's Pointer Fun ( cslibrary.stanford.edu/104 ) est une excellente vidéo sur les pointeurs qui pourraient clarifier les choses. @ Erik- Vous rock pour avoir mis en place le lien Stanford CS Library. Il y a tellement de cadeaux là-bas ...
templatetypedef
6
La réponse de Harry est l'opposé de utile ici.
Jim Balter

Réponses:

731

Révision de la terminologie de base

Il est généralement suffisant - sauf si vous programmez l'assembly - d'envisager un pointeur contenant une adresse mémoire numérique, 1 faisant référence au deuxième octet dans la mémoire du processus, 2 le troisième, 3 le quatrième et ainsi de suite ...

  • 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 façon dont la mémoire et les adresses sont liées, voir «En savoir plus sur les adresses mémoire et pourquoi vous n'avez probablement pas besoin de savoir» à la fin de cette réponse.

Lorsque vous souhaitez accéder aux données / valeurs dans la mémoire vers lesquelles pointe le pointeur - le contenu de l'adresse avec cet index numérique - vous 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 êtes maintenant intéressé par la valeur (actuelle) de l'objet pointé - je me concentre ci-dessous sur C et C ++.

Un scénario de pointeur

Considérez en C, étant donné un pointeur comme pci-dessous ...

const char* p = "abc";

... quatre octets avec les valeurs numériques utilisées pour coder les lettres 'a', 'b', 'c', et un octet 0 pour indiquer la fin des données textuelles, sont stockés quelque part en mémoire et l'adresse numérique de celle-ci les données sont stockées dans p. De cette façon, C code le texte en mémoire est appelé ASCIIZ .

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

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 / identifiant variable pour adresse 0x1000, mais on peut se référer indirectement à la chaîne littérale à l' aide d' un pointeur contenant son adresse: p.

Déréférencer le pointeur

Pour faire référence aux caractères ppointés, nous déréférençons en putilisant l'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érençant 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 vous pouvez é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

Ci-dessus, vous devez avoir su au moment de la compilation que vous auriez besoin d'une variable appelée x, et le code demande au compilateur d'arranger où elle doit être stockée, garantissant que l'adresse sera disponible via &x.

Déréférencement et accès à un membre de données de structure

En C, si vous avez une variable qui est un pointeur vers 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 a également besoin de quelques informations sur le type de données qui est pointé - si ce type de données a besoin de plus d'un octet pour représenter, le pointeur pointe normalement vers l'octet le moins numéroté dans les données.

Donc, en regardant un exemple un peu 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)
++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 dynamiquement

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 y sont lancées ... alors vous pouvez allouer dynamiquement de la mémoire en utilisant malloc. Il est courant de stocker l'adresse dans un pointeur ...

int* p = (int*)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 l' newopé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.

Perte et fuite d'adresses

Souvent, un pointeur peut être la seule indication de l'emplacement de certaines données ou tampons en mémoire. Si une utilisation continue de ces données / tampon est nécessaire, ou la possibilité d'appeler free()ou deleted'éviter une fuite de 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...
free(p);

Pointeurs intelligents C ++

En C ++, il est recommandé d'utiliser des objets de pointeur intelligent pour stocker et gérer les pointeurs, en les désallouant automatiquement lorsque les destructeurs des pointeurs intelligents s'exécutent. Depuis C ++ 11, la bibliothèque standard en fournit deux, unique_ptrcar lorsqu'il n'y a qu'un seul propriétaire pour un objet alloué ...

{
    std::unique_ptr<T> 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_ptrpour l'actionnariat (en utilisant le comptage des références ) ...

{
    auto p = std::make_shared<T>(3.14, "pi");
    number_storage1.may_add(p); // Might copy p into its container
    number_storage2.may_add(p); // Might copy p into its container    } // p's destructor will only delete the T if neither may_add copied it

Pointeurs nuls

En C, NULLet 0- et en plus en C ++ nullptr- peut être utilisé 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 des pointeurs. Par exemple:

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
    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 intégrés ne sont pas nécessairement définis par défaut sur 0, ni boolssur false, les pointeurs ne sont pas toujours définis sur NULL. Tous ceux-ci sont définis sur 0 / false / NULL lorsqu'ils sont des staticvariables ou (C ++ uniquement) des variables membres directes ou indirectes d'objets statiques ou de leurs bases, ou subissent une initialisation nulle (par exemple new T();et new T(x, y, z);effectuent une initialisation nulle sur les membres de T, y compris les pointeurs, tandis que new T;ne fait pas).

En outre, lorsque vous attribuez 0, NULLet 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 faire référence à l'adresse 0 dans votre espace d'adressage virtuel. Le compilateur est autorisé à stocker quelque chose d' autre si elle a des raisons, mais ce qu'il fait - si vous venez le long et comparer le pointeur 0, NULL, nullptrou un autre pointeur qui a été attribué l' un de ceux, le travail doit de comparaison comme prévu. Donc, en dessous du 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 strictement, les pointeurs initialisés stockent un modèle binaire identifiant soit NULLune 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 le processeur peut sélectionner en fonction des registres de "segment" du processeur ou d'une certaine manière de l'ID de segment codé dans la configuration binaire, et / ou en regardant à différents endroits en fonction de la instructions du code machine utilisant l'adresse.

Par exemple, un int*fichier correctement initialisé pour pointer vers une intvariable peut - après avoir été converti en float*- accéder à la mémoire dans la mémoire "GPU", bien distinct de la mémoire où se trouve la intvariable, puis une fois casté et utilisé comme pointeur de fonction, il peut pointer vers plus loin. une mémoire distincte contenant des opcodes de machine pour le programme (avec la valeur numérique du int*pointeur effectivement aléatoire non valide dans ces autres régions de mémoire).

Les langages de programmation 3GL comme C et C ++ ont tendance à masquer cette complexité, de sorte 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 c'est le problème du compilateur si par exemple un registre de segment de CPU particulier doit être restauré à l'avance, ou instruction de code machine distincte utilisée

  • Si vous obtenez un pointeur vers un élément d'un tableau, vous pouvez utiliser l'arithmétique du pointeur pour vous déplacer n'importe où ailleurs dans le tableau, ou même pour former une adresse un-après-la fin du tableau qui est légale à comparer avec d'autres pointeurs vers des éléments 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 après la fin); encore une fois en C et C ++, c'est au compilateur de s'assurer que cela "fonctionne juste"

  • Des fonctions spécifiques du système d'exploitation, par exemple le mappage de la mémoire partagée, peuvent vous fournir des pointeurs, et elles "fonctionneront" dans la plage d'adresses qui leur convient.

  • Les tentatives de déplacer des pointeurs légaux au-delà de ces limites, ou de convertir des nombres arbitraires en pointeurs, ou d'utiliser des pointeurs castés en types non liés, ont généralement un comportement non défini , donc devraient être évitées dans les bibliothèques et applications de niveau supérieur, mais le code pour les systèmes d'exploitation, les pilotes de périphériques, etc. . peut avoir besoin de s'appuyer sur un comportement non défini par la norme C ou C ++, qui est néanmoins bien défini par leur implémentation ou leur matériel spécifique.

Tony Delroy
la source
est p[1] et *(p + 1) identique ? C'est-à-dire, génère- t p[1] - il et *(p + 1)génère- t- il les mêmes instructions?
Pacerier
2
@Pacerier: du 6.5.2.1/2 dans le projet de norme C N1570 (d'abord trouvé en ligne) "La définition de l'opérateur d'indice [] est que E1 [E2] est identique à (* ((E1) + (E2)) ). " - Je ne peux imaginer aucune raison pour laquelle un compilateur ne les convertirait pas immédiatement en représentations identiques à un stade précoce de la compilation, en appliquant les mêmes optimisations après cela, mais je ne vois pas comment quiconque peut définitivement prouver que le code serait identique sans examiner chaque compilateur jamais écrit.
Tony Delroy
3
@Honey: la valeur 1000 hex est trop grande pour être encodée dans un seul octet (8 bits) de mémoire: vous ne pouvez stocker que des nombres non signés de 0 à 255 dans un octet. Ainsi, vous ne pouvez tout simplement pas stocker 1000 hex à "juste" l'adresse 2000. Au lieu de cela, un système 32 bits utiliserait 32 bits - qui est de quatre octets - avec des adresses de 2000 à 2003. Un système 64 bits utiliserait 64 bits - 8 octets - de 2000 à 2007. De toute façon, l'adresse de base de pest juste 2000: si vous aviez un autre pointeur vers lui, pil devrait stocker 2000 dans ses quatre ou huit octets. J'espère que cela pourra aider! À votre santé.
Tony Delroy
1
@TonyDelroy: Si une union ucontient un tableau arr, gcc et clang reconnaîtront que la lvalue u.arr[i]pourrait accéder au même stockage que les autres membres de l'union, mais ne reconnaîtront pas que lvalue *(u.arr+i)pourrait le faire. Je ne sais pas si les auteurs de ces compilateurs pensent que ce dernier invoque UB, ou que le premier invoque UB, mais ils devraient quand même le traiter utilement, mais ils voient clairement les deux expressions comme différentes.
supercat
3
J'ai rarement vu des pointeurs et leur utilisation en C / C ++ expliqués de manière aussi concise et simple.
kayleeFrye_onDeck
102

Déréférencer un pointeur signifie obtenir la valeur qui est stockée dans l'emplacement de mémoire pointé par le pointeur. L'opérateur * est utilisé pour ce faire et est appelé 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.
Mahesh
la source
15
Un pointeur ne pointe pas sur une valeur , il pointe sur un objet .
Keith Thompson
52
@KeithThompson Un pointeur ne pointe pas vers un objet, il pointe vers une adresse mémoire, où se trouve un objet (peut-être une primitive).
mg30rg
4
@ mg30rg: Je ne sais pas quelle distinction vous faites. Une valeur de pointeur est une adresse. Un objet, par définition, est une "région de stockage de données dans l'environnement d'exécution, dont le contenu peut représenter des valeurs". Et qu'entendez-vous par «primitif»? La norme C n'utilise pas ce terme.
Keith Thompson
6
@KeithThompson Je soulignais à peine que vous n'avez pas vraiment ajouté de la valeur à la réponse, vous étiez seulement en train de tergiverser sur la terminologie (et vous avez fait le mal aussi). La valeur du pointeur est sûrement une adresse, c'est ainsi qu'elle "pointe" vers une adresse mémoire. Le mot "objet" dans notre monde OOPdriven peut être trompeur, car il peut être interprété comme "instance de classe" (oui, je ne savais pas que la question est étiquetée [C] et non [C ++]), et j'ai utilisé le mot "primitif" comme à l'opposé de "copmlex" (structure de données comme une structure ou une classe).
mg30rg
3
Permettez-moi d'ajouter à cette réponse que l'opérateur d'indice de tableau []déréférence également un pointeur ( a[b]est défini comme signifiant *(a + b)).
cmaster - réintègre monica le
20

Un pointeur est une "référence" à une valeur. Un peu 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 est physiquement en train de parcourir et de récupérer 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à.

bobobobo
la source
17

En termes simples, le déréférencement signifie accéder à la valeur à partir d'un certain emplacement de mémoire vers lequel ce pointeur pointe.

Fahad Naeem
la source
7

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 - la pointe doit être allouée et le pointeur doit être défini pour pointer dessus. 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 qui a échoué. En Java, la déréférence incorrecte sera signalée poliment par le système d'exécution. Dans les langages compilés tels que C, C ++ et Pascal, la déréférence incorrecte se bloque parfois, et d'autres fois corrompent la mémoire de manière subtile et aléatoire.

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   
}
atp
la source
Vous devez en fait allouer de la mémoire à l'endroit où x est censé pointer. Votre exemple a un comportement indéfini.
Peyman
3

Je pense que toutes les réponses précédentes sont fausses, car elles déclarent que le déréférencement signifie accéder à la valeur réelle. Wikipédia donne plutôt la bonne définition: 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 vers laquelle il pointe. 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 jamais accéder à la valeur. Un tel code ne plantera PAS: le crash se produit lorsque vous accédez réellement aux données par un pointeur non valide. Cependant, malheureusement, selon la norme, déréférencer 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, en bref: déréférencer le pointeur signifie lui appliquer l'opérateur de déréférence. Cet opérateur retourne juste une valeur l pour votre utilisation future.

stsp
la source
eh bien, vous avez déréférencé un pointeur NULL, ce qui entraînerait une erreur de segmentation.
arjun gaur
en plus de cela, vous avez recherché «opérateur de déréférencement» et non «déréférencer un pointeur», ce qui signifie en fait obtenir la valeur / accéder à une valeur à un emplacement de mémoire pointé par un pointeur.
arjun gaur
As-tu essayé? J'ai fait. Ce qui suit ne se bloque pas: `#include <stdlib.h> int main () {char * p = NULL; * p; retourner 0; } `
stsp
1
@stsp Le fait que le code ne plante plus maintenant ne signifie pas qu'il ne le sera plus à l'avenir ou sur un autre système.
1
*p;provoque un comportement indéfini. Bien que vous avez raison déréférencement n'a pas accès à la valeur en soi , le code *p; ne l' accès à la valeur.
MM