web-dev-qa-db-fra.com

Adresse du pointeur dans un tableau multidimensionnel C

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?

28
Ichimonji10

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.

31
Reed Copsey

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.

33
John Bode

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.

5
Alok Singhal

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.

2
dmckee

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.

2
Abhijit K Rao