Je suivais ce tutoriel sur la façon dont un pointeur vers un pointeur .
Permettez-moi de citer le passage pertinent:
int i = 5, j = 6, k = 7; int *ip1 = &i, *ip2 = &j;
Maintenant nous pouvons définir
int **ipp = &ip1;
et
ipp
pointe versip1
quel pointi
.*ipp
estip1
, et**ipp
esti
, ou 5. Nous pouvons illustrer la situation, avec notre notation familière en forme de boîte et de flèche, comme ceci:Si alors on dit
*ipp = ip2;
nous avons changé le pointeur pointé par
ipp
(c'est-à-direip1
) pour qu'il contienne une copie deip2
, de sorte qu'il (ip1
) pointe maintenant versj
:
Ma question est la suivante: pourquoi dans la deuxième image, ipp
pointe toujours ip1
mais pas ip2
?
ipp
lors de sa définition, afin que votre question soit complète ;-)ipp
montrantip1
donc*ipp = ip2
est le même queip1 = ip2
int **ipp
est beaucoup moins intuitif queint** ipp
, en particulier lorsque**ipp
signifie une chose complètement différente en dehors des déclarations de type.int **ipp
semble assez intuitif. Cela signifie que je fais**ipp
unint
. Ce qui est vrai.ipp
estint**
, alors écrivez simplementint**
au lieu de compréhensions magiques "imp
déréférencement est un int" .Réponses:
Oubliez une seconde l'analogie du pointage. Ce qu'un pointeur contient vraiment, c'est une adresse mémoire. L'
&
opérateur est "adresse de" - c'est-à-dire qu'il renvoie l'adresse en mémoire d'un objet. L'*
opérateur vous donne l'objet auquel un pointeur se réfère, c'est-à-dire que, étant donné un pointeur contenant une adresse, il renvoie l'objet à cette adresse mémoire. Ainsi, lorsque vous le faites*ipp = ip2
, ce que vous faites est d'*ipp
obtenir l'objet à l'adresse contenue dansipp
laquelle se trouveip1
, puis de l'affecter àip1
la valeur stockée dansip2
, qui est l'adresse dej
.Simplement
&
-> Adresse de*
-> Valeur àla source
Parce que vous avez modifié la valeur pointée par
ipp
pas la valeur deipp
. Donc,ipp
pointe toujours versip1
(la valeur deipp
),ip1
la valeur de s est maintenant la même queip2
la valeur de, donc ils pointent tous les deux versj
.Ce:
est le même que:
la source
int *ip1 = &i
et*ipp = ip2;
, c'est-à-dire que si vous supprimez leint
de la première instruction, les affectations sont très similaires, mais le*
fait quelque chose de très différent dans les deux cas.Comme la plupart des questions pour débutants dans la balise C, cette question peut être répondue en revenant aux premiers principes:
&
opérateur transforme une variable en pointeur.*
opérateur transforme un pointeur en variable.(Techniquement, je devrais dire "lvalue" au lieu de "variable", mais je pense qu'il est plus clair de décrire les emplacements de stockage modifiables comme des "variables".)
Nous avons donc des variables:
La variable
ip1
contient un pointeur. L'&
opérateur se transformei
en pointeur et cette valeur de pointeur est affectéeip1
.ip1
Contient donc un pointeur versi
.La variable
ip2
contient un pointeur. L'&
opérateur se transformej
en pointeur et ce pointeur est affecté àip2
.ip2
Contient donc un pointeur versj
.La variable
ipp
contient un pointeur. L'&
opérateur transforme la variableip1
en pointeur et cette valeur de pointeur est affectéeipp
.ipp
Contient donc un pointeur versip1
.Résumons l'histoire jusqu'à présent:
i
contient 5j
contient 6ip1
contient "pointeur versi
"ip2
contient "pointeur versj
"ipp
contient "pointeur versip1
"Maintenant on dit
L'
*
opérateur transforme un pointeur en une variable. Nous récupérons la valeur deipp
, qui est "pointeur sur"ip1
et la transformons en une variable. Quelle variable?ip1
Bien sûr!C'est donc simplement une autre façon de dire
Nous récupérons donc la valeur de
ip2
. Qu'Est-ce que c'est? "pointeur versj
". Nous attribuons cette valeur de pointeur àip1
, il enip1
va de même pour le "pointeur versj
"Nous n'avons changé qu'une chose: la valeur de
ip1
:i
contient 5j
contient 6ip1
contient "pointeur versj
"ip2
contient "pointeur versj
"ipp
contient "pointeur versip1
"Une variable change lorsque vous lui assignez. Comptez les affectations; il ne peut pas y avoir plus de changements de variables que d'affectations! Vous commencez par attribuer à
i
,j
,ip1
,ip2
etipp
. Vous attribuez ensuite à*ipp
, ce qui, comme nous l'avons vu, signifie la même chose que «attribuer àip1
». Puisque vous n'avez pas attribué àipp
une deuxième fois, cela n'a pas changé!Si vous vouliez changer,
ipp
vous devrez en fait attribuer àipp
:par exemple.
la source
espérons que ce morceau de code peut vous aider.
il sort:
la source
Mon opinion très personnelle est que les images avec des flèches pointant de cette façon ou qui rendent les pointeurs plus difficiles à comprendre. Cela les fait ressembler à des entités abstraites et mystérieuses. Ils ne sont pas.
Comme tout le reste de votre ordinateur, les pointeurs sont des nombres . Le nom "pointeur" est juste une manière sophistiquée de dire "une variable contenant une adresse".
Par conséquent, permettez-moi de faire bouger les choses en expliquant comment un ordinateur fonctionne réellement.
Nous avons un
int
, il a le nomi
et la valeur 5. Ceci est stocké en mémoire. Comme tout ce qui est stocké en mémoire, il a besoin d'une adresse, sinon nous ne pourrions pas la trouver. Disons qu'ili
se termine à l'adresse 0x12345678 et que son copainj
avec la valeur 6 se termine juste après. En supposant un processeur 32 bits où int vaut 4 octets et les pointeurs sont 4 octets, alors les variables sont stockées dans la mémoire physique comme ceci:Nous voulons maintenant pointer sur ces variables. Nous créons un pointeur vers int
int* ip1
, et unint* ip2
. Comme tout dans l'ordinateur, ces variables de pointeur sont également allouées quelque part en mémoire. Supposons qu'ils se retrouvent aux adresses adjacentes suivantes en mémoire, immédiatement aprèsj
. Nous définissons les pointeurs pour qu'ils contiennent les adresses des variables précédemment allouées:ip1=&i;
("copier l'adresse de i dans ip1") etip2=&j
. Ce qui se passe entre les lignes est:Donc, ce que nous avons obtenu, c'était encore quelques morceaux de mémoire de 4 octets contenant des nombres. Il n'y a pas de flèches mystiques ou magiques en vue.
En fait, rien qu'en regardant une image mémoire, nous ne pouvons pas dire si l'adresse 0x12345680 contient un
int
ouint*
. La différence réside dans la manière dont notre programme choisit d'utiliser le contenu stocké à cette adresse. (La tâche de notre programme est en fait juste de dire au CPU quoi faire avec ces nombres.)Ensuite, nous ajoutons encore un autre niveau d'indirection avec
int** ipp = &ip1;
. Encore une fois, nous obtenons juste un morceau de mémoire:Le modèle semble familier. Encore un autre morceau de 4 octets contenant un nombre.
Maintenant, si nous avions un vidage mémoire de la petite RAM fictive ci-dessus, nous pourrions vérifier manuellement où ces pointeurs pointent. Nous regardons ce qui est stocké à l'adresse de la
ipp
variable et trouvons le contenu 0x12345680. Quelle est bien sûr l'adresse oùip1
est stockée. Nous pouvons aller à cette adresse, vérifier le contenu là-bas et trouver l'adresse dei
, puis enfin nous pouvons aller à cette adresse et trouver le numéro 5.Donc, si nous prenons le contenu de ipp,
*ipp
nous obtiendrons l'adresse de la variable pointeurip1
. En écrivant,*ipp=ip2
nous copions ip2 dans ip1, c'est équivalent àip1=ip2
. Dans les deux cas, nous obtiendrions(Ces exemples ont été donnés pour un processeur big endian)
la source
location, value, variable
emplacement1,2,3,4,5
et la valeurA,1,B,C,3
, l'idée correspondante de pointeurs pourrait être expliquée facilement sans l'utilisation de flèches, qui sont intrinsèquement déroutantes. Quelle que soit l'implémentation choisie, une valeur existe à un endroit donné, et c'est une pièce du puzzle qui devient obscurcie lors de la modélisation avec des flèches.&
opérateur sur une variable vous donne une pièce de monnaie qui représente cette variable. L'*
opérateur sur cette pièce vous rend la variable. Aucune flèche requise!Notez les affectations:
résultats
ipp
à pointerip1
.donc pour
ipp
pointer versip2
, nous devrions changer de la même manière,ce que nous ne faisons clairement pas. Au lieu de cela, nous modifions la valeur à l'adresse pointée par
ipp
.En faisant ce qui suit
nous remplaçons simplement la valeur stockée dans
ip1
.ipp = &ip1
, Des moyens*ipp = ip1 = &i
,maintenant,
*ipp = ip2 = &j
.Donc,
*ipp = ip2
c'est essentiellement le même queip1 = ip2
.la source
Aucune attribution ultérieure n'a modifié la valeur de
ipp
. C'est pourquoi il pointe toujoursip1
.Ce que vous faites avec
*ipp
, c'est -à -dire avecip1
, ne change pas le fait quiipp
pointe versip1
.la source
vous avez placé de belles photos, je vais essayer de faire de beaux ascii
Comme @ Robert-S-Barnes l'a dit dans sa réponse: oubliez les pointeurs , et ce qui indique quoi, mais pensez en termes de mémoire. Fondamentalement, an
int*
signifie qu'il contient l'adresse d'une variable et anint**
contient l'adresse d'une variable qui contient l'adresse d'une variable. Ensuite, vous pouvez utiliser l'algèbre du pointeur pour accéder aux valeurs ou aux adresses:&foo
moyensaddress of foo
et*foo
moyensvalue of the address contained in foo
.Donc, comme les pointeurs concernent la mémoire, la meilleure façon de rendre cela "tangible" est de montrer ce que l'algèbre des pointeurs fait à la mémoire.
Alors, voici la mémoire de votre programme (simplifiée pour les besoins de l'exemple):
lorsque vous faites votre code initial:
voici à quoi ressemble votre mémoire:
là, vous pouvez voir
ip1
etip2
obtenir les adresses dei
etj
etipp
n'existe toujours pas. N'oubliez pas que les adresses sont simplement des entiers stockés avec un type spécial.Ensuite, vous déclarez et définissez
ipp
comme:alors voici votre souvenir:
puis, vous modifiez la valeur pointée par l'adresse stockée dans
ipp
, qui est l'adresse stockée dansip1
:la mémoire du programme est
NB: comme
int*
c'est un type spécial, je préfère toujours éviter de déclarer plusieurs pointeurs sur la même ligne, car je pense que la notationint *x;
ouint *x, *y;
peut être trompeuse. Je préfère écrireint* x; int* y;
HTH
la source
ip2
ne devrait3
pas être4
.Parce que quand tu dis
vous dites «l'objet pointé par
ipp
» pour indiquer la direction de la mémoire quiip2
pointe.Vous ne dites pas
ipp
au pointip2
.la source
Si vous ajoutez l'opérateur de déréférencement
*
au pointeur, vous redirigez du pointeur vers l'objet pointé.Exemples:
Par conséquent:
la source
Si vous voulez
ipp
pointer du doigtip2
, vous devez le direipp = &ip2;
. Cependant, cela laisseraitip1
toujours pointeri
.la source
Au tout début que vous définissez,
Maintenant, déréférencer comme,
la source
Considérez chaque variable représentée comme ceci:
donc vos variables doivent être représentées comme ceci
Comme la valeur de
ipp
est&ip1
ainsi l'inctruction:modifie la valeur de l'addition
&ip1
à la valeur deip2
, ce qui signifieip1
est modifiée:Mais
ipp
encore:Donc, la valeur de
ipp
still,&ip1
ce qui signifie qu'il pointe toujoursip1
.la source
Parce que vous changez le pointeur de
*ipp
. Ça veut direipp
(nom variable) ---- allez à l'intérieur.ipp
trouve l'adresse deip1
.*ipp
, allez à (adresse de l'intérieur)ip1
.Maintenant nous en sommes
ip1
.*ipp
(ieip1
) =ip
2.ip2
contient l'adresse du contenuj
.soip1
sera remplacé par contenir de l'ip2 (c'est-à-dire l'adresse de j), NOUS NE CHANGEONS PAS LEipp
CONTENU. C'EST TOUT.la source
*ipp = ip2;
implique:Affectez
ip2
à la variable pointée paripp
. Donc c'est équivalent à:Si vous souhaitez que l'adresse de
ip2
soit stockéeipp
, faites simplement:Maintenant , les
ipp
points àip2
.la source
ipp
peut contenir la valeur (c'est-à-dire pointer vers) un pointeur vers un objet de type pointeur . Quand tu faispuis le
ipp
contient l' adresse de la variable (pointeur)ip2
, qui est (&ip2
) de type pointeur vers pointeur . Maintenant, la flèche de laipp
deuxième photo pointera versip2
.Wiki dit:
L'
*
opérateur est un opérateur de déréférence qui opère sur une variable de pointeur, et renvoie une valeur l (variable) équivalente à la valeur à l'adresse du pointeur. C'est ce qu'on appelle le déréférencement du pointeur.Appliquer l'
*
opérateur sur leipp
déréfrence à une valeur l de pointeur sur leint
type. La valeur l déréférencée*ipp
est de type pointeur versint
, elle peut contenir l'adresse d'unint
type data. Après la déclarationipp
contient l'adresse deip1
et*ipp
contient l'adresse de (pointant vers)i
. Vous pouvez dire que*ipp
c'est un alias deip1
. Tous les deux**ipp
et*ip1
sont des alias pouri
.En faisant
*ipp
et lesip2
deux pointent vers le même emplacement maisipp
pointent toujours versip1
.En
*ipp = ip2;
fait, il copie le contenu deip2
(l'adresse dej
) versip1
(comme*ipp
un alias pourip1
), créant en fait les deux pointeursip1
etip2
pointant vers le même objet (j
).Ainsi, dans la deuxième figure, la flèche de
ip1
etip2
pointe versj
whileipp
pointe toujours versip1
car aucune modification n'est effectuée pour changer la valeur deipp
.la source