J'étudie un peu le C ++ et je me bats avec des pointeurs. Je comprends que je peux avoir 3 niveaux de pointeurs en déclarant:
int *(*x)[5];
c'est *x
donc un pointeur vers un tableau de 5 éléments qui sont des pointeurs vers int
. Je le sais aussi x[0] = *(x+0);
, x[1] = *(x+1)
et ainsi de suite ...
Alors, compte tenu de la déclaration ci-dessus, pourquoi x[0] != x[0][0] != x[0][0][0]
?
x[0]
,x[0][0]
Etx[0][0][0]
ont différents types. Ils ne peuvent pas être comparés. Que voulez-vous dire!=
?int **x[5]
est un tableau de 5 éléments. Un élément est un pointeur vers un pointeur vers int`int** x[5]
serait un tableau de cinq pointeurs pointant vers des pointeurs pointant vers int.int *(*x)[5]
est un pointeur vers un tableau de cinq pointeurs qui pointent vers int.x[0] != x[0][0] != x[0][0][0]
signifie? Ce n'est pas une comparaison valide en C ++. Même si vous le divisez enx[0] != x[0][0]
etx[0][0] != x[0][0][0]
qu'il n'est toujours pas valide. Alors, que signifie votre question?Réponses:
x
est un pointeur vers un tableau de 5 pointeurs versint
.x[0]
est un tableau de 5 pointeurs versint
.x[0][0]
est un pointeur vers unint
.x[0][0][0]
est unint
.Tu peux voir ça
x[0]
est un tableau et sera converti en pointeur vers son premier élément lorsqu'il est utilisé dans une expression (à quelques exceptions près). Donnera doncx[0]
l'adresse de son premier élémentx[0][0]
qui est0x500
.x[0][0]
contient l'adresse d'unint
qui est0x100
.x[0][0][0]
contient uneint
valeur de10
.Alors,
x[0]
est égal&x[0][0]
et donc&x[0][0] != x[0][0]
.Par conséquent,
x[0] != x[0][0] != x[0][0][0]
.la source
0x100
devrait apparaître immédiatement à gauche de la boîte contenant10
, de la même manière qui0x500
apparaît à gauche de sa boîte. Au lieu d'être loin à gauche, et en dessous.est, selon votre propre message,
qui est simplifié
Pourquoi devrait-il être égal?
Le premier est l'adresse d'un pointeur.
Le second est l'adresse d'un autre pointeur.
Et le troisième est une
int
valeur.la source
x[0][0]
est(x[0])[0]
, c'est*((*(x+0))+0)
-à- dire non*(x+0+0)
. Le déréférencement se produit avant le second[0]
.x[0][0] != *(x+0+0)
tout commex[2][3] != x[3][2]
.Voici la disposition de la mémoire de votre pointeur:
x[0]
renvoie "adresse du tableau",x[0][0]
renvoie "pointeur 0",x[0][0][0]
renvoie «un entier».Je crois que cela devrait être évident maintenant, pourquoi ils sont tous différents.
Ce qui précède est assez proche pour une compréhension de base, c'est pourquoi je l'ai écrit comme je l'ai écrit. Cependant, comme le souligne à juste titre haccks, la première ligne n'est pas précise à 100%. Alors voici tous les petits détails:
D'après la définition du langage C, la valeur de
x[0]
est l'ensemble du tableau de pointeurs entiers. Cependant, les tableaux sont quelque chose avec lequel vous ne pouvez vraiment rien faire en C.Vous manipulez toujours leur adresse ou leurs éléments, jamais le tableau entier dans son ensemble:Vous pouvez passer
x[0]
à l'sizeof
opérateur. Mais ce n'est pas vraiment une utilisation de la valeur, son résultat dépend uniquement du type.Vous pouvez prendre son adresse qui donne la valeur de
x
, c'est-à-dire "adresse du tableau" avec le typeint*(*)[5]
. En d'autres termes:&x[0] <=> &*(x + 0) <=> (x + 0) <=> x
Dans tous les autres contextes , la valeur de
x[0]
se décompose en un pointeur vers le premier élément du tableau. Autrement dit, un pointeur avec la valeur "adresse du tableau" et le typeint**
. L'effet est le même que si vous aviez convertix
en un pointeur de typeint**
.En raison de la décroissance du pointeur de tableau dans le cas 3., toutes les utilisations de
x[0]
aboutissent finalement à un pointeur qui pointe le début du tableau de pointeurs; l'appelprintf("%p", x[0])
imprimera le contenu des cellules de mémoire étiquetées comme "adresse du tableau".la source
x[0]
n'est pas l'adresse du tableau.x[0]
n'est pas l'adresse du tableau, c'est le tableau lui-même. J'ai ajouté une explication approfondie de cela, et de la raison pour laquelle j'ai écrit quex[0]
c'est "l'adresse du tableau". J'espère que tu aimes.printf("%zu\n", sizeof x[0]);
indique la taille du tableau, pas la taille d'un pointeur.sizeof x[0]
...x[0]
déréférence le pointeur le plus à l'extérieur ( pointeur vers le tableau de taille 5 du pointeur vers int) et aboutit à un tableau de taille 5 du pointeur versint
;x[0][0]
déréférence le pointeur le plus à l'extérieur et indexe le tableau, résultant en un pointeur versint
;x[0][0][0]
déréférence tout, ce qui donne une valeur concrète.Au fait, si jamais vous vous sentez confus par ce que signifient ces types de déclarations, utilisez cdecl .
la source
Considérons les expressions étape par étape
x[0]
,x[0][0]
etx[0][0][0]
.Tel que
x
défini de la manière suivantealors expression
x[0]
est un tableau de typeint *[5]
. Tenez compte du fait que l'expressionx[0]
est équivalente à l'expression*x
. C'est déréférencer un pointeur vers un tableau, nous obtenons le tableau lui-même. Dénotons-le comme y c'est-à-dire que nous avons une déclarationL'expression
x[0][0]
est équivalente ày[0]
et a un typeint *
. Dénotons-le comme z c'est-à-dire que nous avons une déclarationexpression
x[0][0][0]
équivaut à expressiony[0][0]
qui à son tour équivaut à expressionz[0]
et a un typeint
.Donc nous avons
x[0]
a le typeint *[5]
x[0][0]
a le typeint *
x[0][0][0]
a le typeint
Ce sont donc des objets de types différents et par le biais de tailles différentes.
Exécuter par exemple
la source
La première chose que je dois dire
De l'image suivante, tout est clair.
C'est juste un exemple, où la valeur de x [0] [0] [0] = 10
et l'adresse de x [0] [0] [0] est 1001
cette adresse est stockée dans x [0] [0] = 1001
et l'adresse de x [0] [0] est 2000
et cette adresse est stockée à x [0] = 2000
Donc x [0] [0] [0] ≠ x [0] [0] ≠ x [0]
.
ÉDITIONS
Programme 1:
Production
Programme 2:
Production
la source
x[0]
ne contient pas d'adresse de fourmi. C'est un tableau. Il se désintégrera pour pointer vers son premier élément.Si vous deviez afficher les tableaux dans une perspective du monde réel, cela apparaîtrait comme suit:
x[0]
est un conteneur de fret rempli de caisses.x[0][0]
est une seule caisse, pleine de boîtes à chaussures, dans le conteneur de fret.x[0][0][0]
est une seule boîte à chaussures à l'intérieur de la caisse, à l'intérieur du conteneur de fret.Même s'il s'agissait de la seule boîte à chaussures dans la seule caisse du conteneur de fret, il s'agit toujours d'une boîte à chaussures et non d'un conteneur de fret
la source
x[0][0]
une seule caisse pleine de morceaux de papier sur lesquels sont écrits les emplacements des boîtes à chaussures?Il y a un principe en C ++ pour que: une déclaration d'une variable indique exactement la manière d'utiliser la variable. Considérez votre déclaration:
qui peut être réécrit comme (pour plus de clarté):
En raison du principe, nous avons:
Par conséquent:
Vous pouvez donc comprendre la différence.
la source
x[0]
est un tableau de 5 pouces, pas un pointeur. (il peut se transformer en pointeur dans la plupart des contextes, mais la distinction est ici importante).*(*x)[5]
est unint
, ainsi(*x)[5]
est unint *
,*x
est donc un(int *)[5]
,x
est donc un*((int *)[5])
. Autrement dit,x
est un pointeur vers un tableau de 5 pointeurs versint
.Vous essayez de comparer différents types par valeur
Si vous prenez les adresses, vous pourriez obtenir plus de ce que vous attendez
Gardez à l'esprit que votre déclaration fait une différence
permettrait aux comparaisons que vous voulez, puisque
y
,y[0]
,y[0][0]
,y[0][0][0]
aurait différentes valeurs et types , mais la même adressen'occupe pas d'espace contigu.
x
etx [0]
ont la même adresse, maisx[0][0]
etx[0][0][0]
sont chacun à des adresses différentesla source
int *(*x)[5]
est différent deint **x[5]
Être
p
un pointeur: vous empilez des déréférences avecp[0][0]
, ce qui équivaut à*((*(p+0))+0)
.En notation C référence (&) et déréférence (*):
Est équivalent à:
Regardez ça, le & * peut être refactoré, il suffit de le supprimer:
la source
p == p
.&(&p[0])[0]
est différent dep[0][0]
Les autres réponses sont correctes, mais aucune d'entre elles ne souligne l'idée qu'il est possible que les trois contiennent la même valeur et qu'elles sont donc incomplètes.
La raison pour laquelle cela ne peut pas être compris à partir des autres réponses est que toutes les illustrations, bien qu'utiles et certainement raisonnables dans la plupart des circonstances, ne couvrent pas la situation où le pointeur
x
pointe vers lui-même.C'est assez facile à construire, mais clairement un peu plus difficile à comprendre. Dans le programme ci-dessous, nous verrons comment nous pouvons forcer les trois valeurs à être identiques.
REMARQUE: le comportement de ce programme n'est pas défini, mais je le publie ici uniquement comme une démonstration intéressante de quelque chose que les pointeurs peuvent faire, mais ne devraient pas .
Cela se compile sans avertissements en C89 et C99, et la sortie est la suivante:
Fait intéressant, les trois valeurs sont identiques. Mais cela ne devrait pas être une surprise! Tout d'abord, décomposons le programme.
Nous déclarons
x
comme un pointeur vers un tableau de 5 éléments où chaque élément est de type pointeur vers int. Cette déclaration alloue 4 octets sur la pile d'exécution (ou plus selon votre implémentation; sur ma machine, les pointeurs sont de 4 octets), doncx
fait référence à un emplacement mémoire réel. Dans la famille de langages C, le contenu dex
n'est que des ordures, quelque chose qui reste d'une utilisation précédente de l'emplacement, doncx
lui-même ne pointe nulle part - certainement pas vers l'espace alloué.Donc, naturellement, nous pouvons prendre l'adresse de la variable
x
et la mettre quelque part, c'est donc exactement ce que nous faisons. Mais nous allons continuer et le mettre dans x lui-même. Puisque&x
a un type différent de celuix
, nous devons faire un casting pour ne pas recevoir d'avertissements.Le modèle de mémoire ressemblerait à ceci:
Ainsi, le bloc de mémoire de 4 octets à l'adresse
0xbfd9198c
contient le modèle de bits correspondant à la valeur hexadécimale0xbfd9198c
. Assez simple.Ensuite, nous imprimons les trois valeurs. Les autres réponses expliquent à quoi chaque expression fait référence, donc la relation devrait être claire maintenant.
Nous pouvons voir que les valeurs sont les mêmes, mais seulement dans un sens de très bas niveau ... leurs modèles de bits sont identiques, mais le type de données associé à chaque expression signifie que leurs valeurs interprétées sont différentes. Par exemple, si nous avons imprimé en
x[0][0][0]
utilisant la chaîne de format%d
, nous obtiendrions un nombre négatif énorme, donc les «valeurs» sont, en pratique, différentes, mais le modèle de bits est le même.C'est en fait très simple ... dans les diagrammes, les flèches pointent simplement vers la même adresse mémoire plutôt que vers des adresses différentes. Cependant, alors que nous avons pu forcer un résultat attendu hors d'un comportement non défini, c'est juste cela - non défini. Ce n'est pas du code de production, mais simplement une démonstration par souci d'exhaustivité.
Dans une situation raisonnable, vous utiliserez
malloc
pour créer le tableau de 5 pointeurs int et à nouveau pour créer les entiers pointés dans ce tableau.malloc
renvoie toujours une adresse unique (sauf si vous n'avez plus de mémoire, auquel cas elle renvoie NULL ou 0), vous n'aurez donc jamais à vous soucier des pointeurs auto-référentiels comme celui-ci.J'espère que c'est la réponse complète que vous recherchez. Vous ne devriez pas vous attendre
x[0]
,x[0][0]
etx[0][0][0]
être égal, mais ils pourraient l'être si vous y êtes forcé. Si quelque chose vous a dépassé, faites-le moi savoir pour que je puisse clarifier!la source
x[0]
ne représente pas réellement un objet valide du type correctx
est un pointeur vers un tableau, nous pouvons donc utiliser l'[]
opérateur pour spécifier un décalage à partir de ce pointeur et le déréférencer. Qu'est-ce qui est étrange là-bas? Le résultat dex[0]
est un tableau, et C ne se plaint pas si vous imprimez cela en utilisant%p
puisque c'est ainsi qu'il est implémenté en dessous de toute façon.-pedantic
drapeau ne produit aucun avertissement, donc C est bien avec les types ...Le type de
int *(*x)[5]
estint* (*)[5]
ie un pointeur vers un tableau de 5 pointeurs vers des entiers.x
est l'adresse du premier tableau de 5 pointeurs vers les entiers (une adresse de typeint* (*)[5]
)x[0]
l'adresse du premier tableau de 5 pointeurs vers ints (même adresse avec le typeint* [5]
) (offset adresse x par0*sizeof(int* [5])
ie index * taille-du-type-pointé et déréférencement)x[0][0]
est le premier pointeur vers un int dans le tableau (la même adresse avec le typeint*
) (adresse de décalage x par0*sizeof(int* [5])
et déréférencement, puis par0*sizeof(int*)
et déréférence)x[0][0][0]
est le premier entier pointé par le pointeur vers un int (adresse de décalage x par0*sizeof(int* [5])
et déréférencement et décalage de cette adresse par0*sizeof(int*)
et déréférence et décalage de cette adresse par0*sizeof(int)
et déréférence)Le type de
int *(*y)[5][5][5]
estint* (*)[5][5][5]
ie un pointeur vers un tableau 3D de pointeurs 5x5x5 vers les entiersx
est l'adresse du premier tableau 3D de pointeurs 5x5x5 vers les entiers de typeint*(*)[5][5][5]
x[0]
est l'adresse du premier tableau 3D de pointeurs 5x5x5 vers ints (adresse de décalage x par0*sizeof(int* [5][5][5])
et déréférencement)x[0][0]
est l'adresse du premier tableau 2D de pointeurs 5x5 vers ints (adresse de décalage x de0*sizeof(int* [5][5][5])
et déréférencement puis décalage de cette adresse de0*sizeof(int* [5][5])
)x[0][0][0]
est l'adresse du premier tableau de 5 pointeurs vers les entiers (adresse de décalage x de0*sizeof(int* [5][5][5])
et déréférencement et décalage de cette adresse de0*sizeof(int* [5][5])
et décalage de cette adresse de0*sizeof(int* [5])
)x[0][0][0][0]
est le premier pointeur vers un entier dans le tableau (adresse de décalage x de0*sizeof(int* [5][5][5])
et déréférencement et décalage de cette adresse de0*sizeof(int* [5][5])
et décalage de cette adresse de0*sizeof(int* [5])
et décalage de cette adresse de0*sizeof(int*)
et déréférence)x[0][0][0][0][0]
est le premier entier pointé par le pointeur vers un int (adresse de décalage x de0*sizeof(int* [5][5][5])
et déréférencement et décalage de cette adresse0*sizeof(int* [5][5])
et décalage de cette adresse de0*sizeof(int* [5])
et décalage de cette adresse de0*sizeof(int*)
et déréférence et décalage de cette adresse de0*sizeof(int)
et déréférence)Quant à la désintégration du tableau:
Cela équivaut à passer
int* x[][5][5]
ouint* (*x)[5][5]
c'est -à- dire qu'ils se désintègrent tous vers ce dernier. C'est pourquoi vous n'obtiendrez pas d'avertissement du compilateur pour l'utilisationx[6][0][0]
dans la fonction, mais vous le ferezx[0][6][0]
parce que les informations de taille sont conservéesx[0]
est l'adresse du premier tableau 3D de pointeurs 5x5x5 vers les entiersx[0][0]
est l'adresse du premier tableau 2D de pointeurs 5x5 vers les entiersx[0][0][0]
est l'adresse du premier tableau de 5 pointeurs vers les entiersx[0][0][0][0]
est le premier pointeur vers un int dans le tableaux[0][0][0][0][0]
est le premier int pointé par le pointeur vers un intDans le dernier exemple, il est sémantiquement beaucoup plus clair à utiliser
*(*x)[0][0][0]
quex[0][0][0][0][0]
, car le premier et le dernier[0]
ici sont interprétés comme un déréférencement de pointeur plutôt qu'un index dans un tableau multidimensionnel, en raison du type. Ils sont cependant identiques car(*x) == x[0]
quelle soit la sémantique. Vous pouvez également utiliser*****x
, ce qui donnerait l'impression que vous déréférencer le pointeur 5 fois, mais il est en fait interprété exactement de la même manière: un décalage, une déréférence, une déréférence, 2 décalages dans un tableau et une déréférence, uniquement à cause du type vous appliquez l'opération à.Essentiellement, lorsque vous
[0]
ou*
un*
à un type non tableau, il s'agit d'un décalage et d'une déréférence en raison de l'ordre de priorité de*(a + 0)
.Lorsque vous
[0]
ou*
un*
type de tableau, c'est un offset puis une déréférence idempotente (la déréférence est résolue par le compilateur pour donner la même adresse - c'est une opération idempotente).Lorsque vous
[0]
ou*
un type avec un type de tableau 1d, c'est un décalage puis une déréférenceSi vous
[0]
ou**
un type de tableau 2D, il s'agit uniquement d'un décalage, c'est-à-dire d'un décalage puis d'un déréférencement idempotent.Si vous
[0][0][0]
ou***
un type de tableau 3D, alors c'est un décalage + déréférencement idempotent puis un déréférencement offset + idempotent puis un déréférencement offset + idempotent puis un déréférencement. Le véritable déréférencement se produit uniquement lorsque le type de tableau est entièrement supprimé.Pour l'exemple du
int* (*x)[1][2][3]
type est déroulé dans l'ordre.x
a un typeint* (*)[1][2][3]
*x
a un typeint* [1][2][3]
(offset 0 + déréférencement idempotent)**x
a un typeint* [2][3]
(offset 0 + déréférencement idempotent)***x
a un typeint* [3]
(offset 0 + déréférencement idempotent)****x
a un typeint*
(offset 0 + déréférence)*****x
a le typeint
(décalage 0 + déréférence)la source