Je déconne avec des tableaux et des pointeurs multidimensionnels. J'ai regardé un programme qui imprime le contenu et les adresses d'un simple tableau. Voici ma déclaration de tableau:
int zippo[4][2] = { {2,4},
{6,8},
{1,3},
{5,7} };
Ma compréhension actuelle est que zippo
est un pointeur et qu'il peut contenir l'adresse de quelques autres pointeurs. Par défaut, zippo
contient l'adresse du pointeur zippo[0]
, et il peut également contenir les adresses des pointeurs zippo[1]
, zippo[2]
, et zippo[3]
.
Maintenant, prenez la déclaration suivante:
printf("zippo[0] = %p\n", zippo[0]);
printf(" *zippo = %p\n", *zippo);
printf(" zippo = %p\n", zippo);
Sur ma machine, cela donne la sortie suivante:
zippo[0] = 0x7fff170e2230
*zippo = 0x7fff170e2230
zippo = 0x7fff170e2230
Je comprends parfaitement pourquoi zippo[0]
et *zippo
ont la même valeur. Ce sont deux pointeurs, et ils stockent tous les deux l'adresse (par défaut) de l'entier 2, ou zippo[0][0]
. Mais que se passe-t-il avec zippo
partageant également la même adresse mémoire? zippo
ne devrait-il pas stocker l'adresse du pointeur zippo[0]
? Whaaaat?
Lorsque vous déclarez un tableau multidimensionnel, le compilateur le traite comme un tableau unidimensionnel. Les tableaux multidimensionnels ne sont qu'une abstraction pour nous faciliter la vie. Vous avez un malentendu: ce n'est pas un tableau pointant vers 4 tableaux, c'est toujours juste un seul bloc de mémoire contigus.
Dans votre cas, faire:
int zippo[4][2]
C'est vraiment la même chose que de faire
int zippo[8]
Avec les calculs requis pour l'adressage 2D géré par le compilateur.
Pour plus de détails, voir ceci tutoriel sur les tableaux en C++.
C'est très différent de faire:
int** zippo
ou
int* zippo[4]
Dans ce cas, vous créez un tableau de quatre pointeurs, qui pourraient être alloués à d'autres tableaux.
Lorsqu'une expression de tableau apparaît dans la plupart des contextes, son type est implicitement converti du "tableau à N éléments de T" en "pointeur vers T", et sa valeur est définie pour pointer vers le premier élément du tableau. Les exceptions à cette règle sont lorsque l'expression de tableau est un opérande des opérateurs sizeof
ou address-of (&
), Ou lorsque le tableau est un littéral de chaîne utilisé comme initialiseur dans une déclaration.
Ainsi, l'expression zippo
"se désintègre" du type int [4][2]
(Tableau à 4 éléments de tableaux à 2 éléments d'int) à int (*)[2]
(pointeur vers le tableau à 2 éléments de int). De même, le type de zippo[0]
Est int [2]
, Qui est implicitement converti en int *
.
Étant donné la déclaration int zippo[4][2]
, Le tableau suivant montre les types de diverses expressions de tableau impliquant des zippo et toutes les conversions implicites:
Type d'expression Implicitement converti en expression équivalente ---------- ---- ------------------- ---- --------------------- zippo int [4] [2] int (*) [2] & zippo int (*) [4] [2] * zippo int [2] int * zippo [0] zippo [i] int [2] int * & zippo [ i] int (*) [2] * zippo [i] int zippo [i] [0] zippo [i] [j] int & zippo [i] [ j] int * * zippo [i] [j] invalide
Notez que zippo
, &zippo
, *zippo
, zippo[0]
, &zippo[0]
Et &zippo[0][0]
Ont tous la même valeur; ils pointent tous vers la base du tableau (l'adresse du tableau est la même que l'adresse du premier élément du tableau). Les types des diverses expressions diffèrent cependant tous.
zippo
n'est pas un pointeur. C'est un tableau de valeurs de tableau. zippo
et zippo[i]
pour i
dans 0..4 peuvent "se désintégrer" vers un pointeur dans certains cas (en particulier, dans des contextes de valeur). Essayez d'imprimer sizeof zippo
Pour un exemple d'utilisation de zippo
dans un contexte sans valeur. Dans ce cas, sizeof
indiquera la taille du tableau, pas la taille d'un pointeur.
Le nom d'un tableau, dans les contextes de valeur , se désintègre en un pointeur sur son premier élément. Ainsi, dans le contexte de la valeur, zippo
est identique à &zippo[0]
, Et a donc le type "pointeur vers un tableau [2] de int
"; *zippo
, Dans le contexte de la valeur est identique à &zippo[0][0]
, C'est-à-dire "pointeur vers int
". Ils ont la même valeur, mais des types différents.
Je recommande la lecture Tableaux et pointeurs pour répondre à votre deuxième question. Les pointeurs ont la même "valeur", mais pointent vers différentes quantités d'espace. Essayez d'imprimer zippo+1
Et *zippo+1
Pour voir cela plus clairement:
#include <stdio.h>
int main(void)
{
int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };
printf("%lu\n", (unsigned long) (sizeof zippo));
printf("%p\n", (void *)(zippo+1));
printf("%p\n", (void *)(*zippo+1));
return 0;
}
Pour ma course, il imprime:
32
0xbffede7c
0xbffede78
Me disant que sizeof(int)
sur ma machine est 4, et que les deuxième et troisième pointeurs n'ont pas la même valeur (comme prévu).
De plus, le spécificateur de format "%p"
A besoin de void *
Dans les fonctions *printf()
, vous devez donc caster vos pointeurs sur void *
Dans vos appels printf()
(printf()
est une fonction variadique, donc le compilateur ne peut pas faire la conversion automatique pour vous ici).
Edit : Quand je dis qu'un tableau "se désintègre" à un pointeur, je veux dire que le nom d'un tableau dans un contexte de valeur est équivalent à un pointeur. Ainsi, si j'ai T pt[100];
Pour un type T
, alors le nom pt
est de type T *
Dans des contextes de valeur. Pour les opérateurs sizeof
et unaires &
, Le nom pt
ne se réduit pas à un pointeur. Mais vous pouvez faire T *p = pt;
- c'est parfaitement valable car dans ce contexte, pt
est de type T *
.
Notez que cette "décomposition" ne se produit qu'une seule fois. Disons donc que nous avons:
int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };
Ensuite, zippo
dans le contexte de valeur se désintègre en un pointeur de type: pointeur vers le tableau [2] de int
. Dans du code:
int (*p1)[2] = zippo;
est valide, alors que
int **p2 = zippo;
déclenchera un avertissement "affectation de pointeur incompatible".
Avec zippo
défini comme ci-dessus,
int (*p0)[4][2] = &zippo;
int (*p1)[2] = zippo;
int *p2 = zippo[0];
sont tous valides. Ils doivent imprimer la même valeur lorsqu'ils sont imprimés à l'aide de printf("%p\n", (void *)name);
, mais les pointeurs sont différents en ce qu'ils pointent vers la matrice entière, une ligne et un seul entier respectivement.
L'important ici est que int zippy[4][2]
n'est pas le même type d'objet que int **zippo
.
Juste comme int zippi[5]
, zippy
est l'adresse d'un bloc de mémoire. Mais le compilateur sait que vous voulez adresser les huit emplacements mémoire commençant à zippy
avec une syntaxe bidimensionnelle, mais que vous voulez adresser les cinq emplacements mémoire commençant à zippi
avec une syntaxe unidimensionnelle.
zippo
est une tout autre chose. It contient l'adresse d'un bloc de mémoire suffisamment grand pour contenir deux pointeurs, et si vous faites them pointer vers certains tableaux d'entiers, vous pouvez les déréférencer avec les deux dimensions syntaxe d'accès au tableau.
Très bien expliqué par Reed, j'ajouterai quelques points supplémentaires pour le simplifier, quand on se réfère à zippo
ou zippo[0]
ou zippo[0][0]
, nous faisons toujours référence à la même adresse de base du tableau zippo
. La raison pour laquelle les tableaux sont toujours des blocs de mémoire contigus et les tableaux multidimensionnels sont des tableaux à plusieurs dimensions placés en continu.
Lorsque vous devez incrémenter de chaque ligne, vous avez besoin d'un pointeur int *p = &zippo[0][0]
, et faire p++
incrémente le pointeur de chaque ligne. Dans votre exemple, c'est un tableau 4 X 2, en faisant p++
its, le pointeur pointe actuellement sur le deuxième ensemble de 4 éléments.