Veuillez inclure un exemple avec l'explication.
int *p;
définirait un pointeur sur un entier et *p
déréférencerait ce pointeur, ce qui signifie qu'il récupérerait en fait les données vers lesquelles p pointe.
Veuillez inclure un exemple avec l'explication.
int *p;
définirait un pointeur sur un entier et *p
déréférencerait ce pointeur, ce qui signifie qu'il récupérerait en fait les données vers lesquelles p pointe.
Réponses:
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 ...
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 ++.
Considérez en C, étant donné un pointeur comme p
ci-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 p
un 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
.
Pour faire référence aux caractères p
pointés, nous déréférençons en p
utilisant 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
.
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_
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]
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' 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.
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 delete
d'é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);
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_ptr
car 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_ptr
pour 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
En C, NULL
et 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 bools
sur 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 static
variables 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
, 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 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
, nullptr
ou 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 ++ ...
Plus strictement, les pointeurs initialisés stockent un modèle binaire identifiant soit NULL
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 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 int
variable 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 int
variable, 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.
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?
p
est juste 2000: si vous aviez un autre pointeur vers lui, p
il devrait stocker 2000 dans ses quatre ou huit octets. J'espère que cela pourra aider! À votre santé.
u
contient 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.
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.
[]
déréférence également un pointeur ( a[b]
est défini comme signifiant *(a + b)
).
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à.
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.
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
}
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.
*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.