web-dev-qa-db-fra.com

Pourquoi x [0]! = X [0] [0]! = X [0] [0] [0]?

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];

de sorte que *x est un pointeur sur un tableau de 5 éléments pointeurs vers int. Aussi, je sais que x[0] = *(x+0);, x[1] = *(x+1) et ainsi de suite ....

Donc, étant donné la déclaration ci-dessus, pourquoi x[0] != x[0][0] != x[0][0][0]?

148
Leo91

x est un pointeur sur un tableau de 5 pointeurs vers int.
x[0] _ est un tablea de 5 pointeurs vers int.
x[0][0] est un pointeur sur un int.
x[0][0][0] est un int.

                       x[0]
   Pointer to array  +------+                                 x[0][0][0]         
x -----------------> |      |         Pointer to int           +-------+
               0x500 | 0x100| x[0][0]---------------->   0x100 |  10   |
x is a pointer to    |      |                                  +-------+
an array of 5        +------+                        
pointers to int      |      |         Pointer to int                             
               0x504 | 0x222| x[0][1]---------------->   0x222                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x508 | 0x001| x[0][2]---------------->   0x001                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x50C | 0x123| x[0][3]---------------->   0x123                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x510 | 0x000| x[0][4]---------------->   0x000                    
                     |      |                                             
                     +------+                                             

Tu peux voir ça

  • x[0] est un tableau et sera converti en pointeur sur son premier élément lorsqu’il est utilisé dans une expression (à quelques exceptions près). Donc x[0] donnera l'adresse de son premier élément x[0][0] lequel est 0x500.
  • x[0][0] contient l'adresse d'un int qui est 0x100.
  • x[0][0][0] contient une valeur int de 10.

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].

258
haccks
x[0] != x[0][0] != x[0][0][0]

est, selon votre propre post,

*(x+0) != *(*(x+0)+0) != *(*(*(x+0)+0)+0)`  

qui est simplifié

*x != **x != ***x

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 quelque int valeur.

133
deviantfan

Voici la disposition de la mémoire de votre pointeur:

   +------------------+
x: | address of array |
   +------------------+
            |
            V
            +-----------+-----------+-----------+-----------+-----------+
            | pointer 0 | pointer 1 | pointer 2 | pointer 3 | pointer 4 |
            +-----------+-----------+-----------+-----------+-----------+
                  |
                  V
                  +--------------+
                  | some integer |
                  +--------------+

x[0] Donne "adresse de tableau",
x[0][0] Donne "le pointeur 0",
x[0][0][0] Donne "un entier".

Je crois, cela devrait être évident maintenant, pourquoi ils sont tous différents.


Ce qui précède est assez proche de la 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 détails:

Dans 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 que vous ne pouvez pas vraiment faire en C. Vous manipulez toujours leur adresse ou leurs éléments, jamais le tableau entier dans son ensemble:

  1. Vous pouvez passer x[0] À l'opérateur sizeof. Mais ce n'est pas vraiment une utilisation de la valeur, son résultat dépend uniquement du type.

  2. Vous pouvez prendre son adresse qui donne la valeur de x, i. e. "adresse du tableau" de type int*(*)[5]. En d'autres termes: &x[0] <=> &*(x + 0) <=> (x + 0) <=> x

  3. Dans tous les autres contextes, la valeur de x[0] Se décomposera en un pointeur sur le premier élément du tableau. C'est-à-dire un pointeur avec la valeur "address of array" et le type int**. L'effet est le même que si vous aviez jeté x sur un pointeur de type int**.

En raison de la décroissance du pointeur de tableau dans le cas 3., toutes les utilisations de x[0] Génèrent un pointeur indiquant le début du tableau de pointeurs; l'appel printf("%p", x[0]) imprimera le contenu des cellules de mémoire étiquetées "adresse du tableau".

49
cmaster
  • x[0] déréférence le pointeur le plus externe ( pointeur au tableau de taille 5 du pointeur sur int) et donne un tableau de taille 5 de pointeur à int;
  • x[0][0] déréférence le pointeur le plus externe et indexe le tableau, ce qui donne un pointeur sur int;
  • x[0][0][0] déréférence tout, donnant une valeur concrète.

À propos, si vous vous sentez un peu déconcerté par la signification de ces déclarations, utilisez cdecl .

18
d125q

Voyons maintenant les expressions pas à pas x[0], x[0][0] et x[0][0][0].

Comme x est défini de la manière suivante

int *(*x)[5];

puis expression x[0] est un tableau de type int *[5]. Prendre en compte cette expression x[0] est équivalent à expression *x. C'est-à-dire qu'en déréférencant un pointeur sur un tableau, nous obtenons le tableau lui-même. Laissons le noter comme y c'est que nous avons une déclaration

int * y[5];

Expression x[0][0] est équivalent à y[0] et a le type int *. Soit z comme c'est-à-dire que nous avons une déclaration

int *z;

expression x[0][0][0] est équivalent à expression y[0][0] qui équivaut à l'expression z[0] et a le type int.

Nous avons donc

x[0] a le type int *[5]

x[0][0] a le type int *

x[0][0][0] a le type int

Ce sont donc des objets de différents types et, au passage, de différentes tailles.

Courir par exemple

std::cout << sizeof( x[0] ) << std::endl;
std::cout << sizeof( x[0][0] ) << std::endl;
std::cout << sizeof( x[0][0][0] ) << std::endl;
11
Vlad from Moscow

La première chose que je dois dire

x [0] = * (x + 0) = * x;

x [0] [0] = * (* (x + 0) + 0) = * * x;

x [0] [0] [0] = * (* (* (x + 0) + 0)) = * * * x;

Donc * x ≠ * * x * * * * x

De l'image suivante, tout est clair.

  x[0][0][0]= 2000

  x[0][0]   = 1001

  x[0]      = 10

enter image description here

C'est juste un exemple, où la valeur de x [0] [0] [0] = 1

et l'adresse de x [0] [0] [0] est 1001

cette adresse est stockée dans x [0] [0] = 1001

et adresse de x [0] [0] est 20

et cette adresse est stockée à x [0] = 20

Donc x [0] [0] [0]x [0] [0] - x [0]

.

[~ # ~] éditions [~ # ~]

Programme 1:

{
int ***x;
x=(int***)malloc(sizeof(int***));
*x=(int**)malloc(sizeof(int**));
**x=(int*)malloc(sizeof(int*));
***x=10;
printf("%d   %d   %d   %d\n",x,*x,**x,***x);
printf("%d   %d   %d   %d   %d",x[0][0][0],x[0][0],x[0],x,&x);
}

Sortie

142041096 142041112 142041128 10
10 142041128 142041112 142041096 -1076392836

Programme 2:

{
int x[1][1][1]={10};
printf("%d   %d   %d   %d \n ",x[0][0][0],x[0][0],x[0],&x);
}

Sortie

10   -1074058436   -1074058436   -1074058436 
10
apm

Si vous deviez voir les tableaux d'un point de vue du monde réel, cela ressemblerait à ceci:

x[0] est un conteneur de fret rempli de caisses.
x[0][0] est une caisse unique, pleine de boîtes à chaussures, dans le conteneur.
x[0][0][0] est une boîte à chaussures unique dans la caisse, dans le conteneur.

Même si c’était la seule boîte à chaussures dans la seule caisse du conteneur, c’est quand même une boîte à chaussures et non un conteneur.

Il y a un principe en C++ qui dit que: une déclaration d'une variable indique exactement le mode d'utilisation de la variable. Considérez votre déclaration:

int *(*x)[5];

qui peut être réécrit comme (pour plus clair):

int *((*x)[5]);

En raison du principe, nous avons:

*((*x)[i]) is treated as an int value (i = 0..4)
→ (*x)[i] is treated as an int* pointer (i = 0..4)
→ *x is treated as an int** pointer
→ x is treated as an int*** pointer

Par conséquent:

x[0] is an int** pointer
→ x[0][0] = (x[0]) [0] is an int* pointer
→ x[0][0][0] = (x[0][0]) [0] is an int value

Donc, vous pouvez comprendre la différence.

4
Nghia Bui

Être p un pointeur: vous empilez des déréférences avec p[0][0], Ce qui équivaut à *((*(p+0))+0).

En notation C (&) et déréférencement (*):

p == &p[0] == &(&p[0])[0] == &(&(&p[0])[0])[0])

Est équivalent à:

p == &*(p+0) == &*(&*(p+0))+0 == &*(&*(&*(p+0))+0)+0

Regardez ça, le & * peut être refait, en le retirant simplement:

p == p+0 == p+0+0 == p+0+0+0 == (((((p+0)+0)+0)+0)+0)
2
Luciano

Les autres réponses sont correctes, mais aucune d’entre elles ne souligne l’idée que il est possible que les trois contiennent la même valeur, et sont donc en quelque sorte incomplètes.

La raison pour laquelle cela ne peut pas être compris dans les autres réponses est que toutes les illustrations, bien que utiles et certainement raisonnables dans la plupart des circonstances, ne couvrent pas la situation dans laquelle le pointeur x pointe sur 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.

NOTE: Le comportement de ce programme n’est pas défini, mais je le publie ici uniquement à titre de démonstration intéressante de quelque chose que des pointeurs peuvent do, mais ne devrait pas .

#include <stdio.h>

int main () {
  int *(*x)[5];

  x = (int *(*)[5]) &x;

  printf("%p\n", x[0]);
  printf("%p\n", x[0][0]);
  printf("%p\n", x[0][0][0]);
}

Cela compile sans avertissements dans C89 et C99, et le résultat est le suivant:

$ ./ptrs
0xbfd9198c
0xbfd9198c
0xbfd9198c

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 en tant que pointeur sur un tableau de 5 éléments, chaque élément étant de type pointeur sur int. Cette déclaration alloue 4 octets sur la pile d'exécution (ou plus en fonction de votre implémentation; les pointeurs de ma machine sont 4 octets), donc x fait référence à un emplacement de mémoire réel. Dans la famille de langues C, le contenu de x n'est qu'une ordure, quelque chose qui reste de l'utilisation antérieure de l'emplacement. Ainsi, x ne pointe pas nulle part - et certainement pas sur 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 aller de l'avant et le mettre dans x lui-même. Comme &x A un type différent de x, nous devons faire un casting pour ne pas recevoir d'avertissements.

Le modèle de mémoire ressemblerait à quelque chose comme ceci:

0xbfd9198c
+------------+
| 0xbfd9198c |
+------------+

Ainsi, le bloc de mémoire de 4 octets à l'adresse 0xbfd9198c Contient le modèle de bits correspondant à la valeur hexadécimale 0xbfd9198c. Assez simple.

Ensuite, nous imprimons les trois valeurs. Les autres réponses expliquent le contenu de chaque expression. La relation doit donc être claire maintenant.

Nous pouvons voir que les valeurs sont les mêmes, mais uniquement dans un sens très bas ... leurs modèles de bits sont identiques, mais les données de type associées à chaque expression signifient que leurs valeurs interprétées sont différentes. Par exemple, si nous imprimions x[0][0][0] En utilisant la chaîne de format %d, Nous aurions un nombre négatif énorme, de sorte que les "valeurs" sont, en pratique, différentes, mais le motif de bits est le même.

C’est en fait très simple… dans les diagrammes, les flèches indiquent simplement la même adresse mémoire que des adresses différentes. Cependant, même si nous avons réussi à forcer un résultat attendu à sortir d'un comportement indéfini, c'est simplement cela - indéfini. Ce n'est pas un 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, puis à nouveau pour créer les ints pointés dans ce tableau. malloc renvoie toujours une adresse unique (sauf si vous manquez de mémoire, auquel cas il renvoie NULL ou 0), vous n'aurez donc jamais à vous soucier de 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 à ce que x[0], x[0][0] Et x[0][0][0] Soient égaux, mais ils pourraient l'être s'ils étaient forcés. Si quelque chose vous échappait, faites le moi savoir afin que je puisse clarifier la situation!

1
Purag

Vous essayez de comparer différents types par valeur

Si vous prenez les adresses, vous obtiendrez peut-être plus que ce que vous attendez

Gardez à l'esprit que votre déclaration fait la différence

 int y [5][5][5];

permettrait les comparaisons que vous voulez, puisque y, y[0], y[0][0], y[0][0][0] aurait des valeurs et des types différents mais la même adresse

int **x[5];

n'occupe pas d'espace contigu.

x et x [0] ont la même adresse, mais x[0][0] et x[0][0][0] sont chacun à des adresses différentes

1
Glenn Teitelbaum