Confusion sur les pointeurs et les tableaux multidimensionnels

Si ce qui suit est possible:

MyFunction(int *array, int size) { for(int i=0 ; i<size ; i++) { printf(“%d”, array[i]); } } main() { int array[6] = {0, 1, 2, 3, 4, 5}; MyFunction(array, 6); } 

Pourquoi ce qui suit n’est pas?

 MyFunction(int **array, int row, int col) { for(int i=0 ; i<row ; i++) { for(int j=0 ; j<col ; j++) { printf(“%d”, array[i][j]); } } } main() { int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; MyFunction(array, 3, 3); } 

Tout d’abord, un langage standard :

6.3.2.1 Lvalues, tableaux et désignateurs de fonctions

3 Sauf s’il s’agit de l’opérande de l’opérateur sizeof ou de l’opérateur unary &, ou s’il s’agit d’un littéral utilisé pour initialiser un tableau, une expression de type “tableau de type” est convertie en une expression de type “pointeur vers type” qui pointe sur l’élément initial de l’object tableau et n’est pas une lvalue. Si l’object tableau a une classe de stockage de registre, le comportement n’est pas défini.

Compte tenu de la déclaration

 int myarray[3][3]; 

le type de myarray est “tableau à 3 éléments d’un tableau à 3 éléments d’ int “. En suivant la règle ci-dessus, quand vous écrivez

 MyFunction(myarray, 3, 3); 

l’ expression myarray a son type implicitement converti (“decay”) de “tableau à 3 éléments de tableau à 3 éléments d’ int ” en “pointeur sur tableau de 3 éléments à int “, ou int (*)[3] .

Ainsi, votre prototype de fonction devra être

 int MyFunction(int (*array)[3], int row, int col) 

Notez que int **array n’est pas la même chose que int (*array)[3] ; l’arithmétique du pointeur sera différente, afin que vos indices ne finissent pas par pointer aux bons endroits. Rappelez-vous que l’indexation de tableau est définie en termes d’arithmétique de pointeur: a[i] == *(a+i) , a[i][j] == *(*(a + i) + j) . a+i donnera une valeur différente selon que a est un int ** ou un int (*)[N] .

Cet exemple particulier suppose que vous passez toujours un tableau de Nx3-element int ; pas très flexible si vous voulez traiter avec n’importe quel tableau de taille NxM. Une façon de contourner ce problème consiste à transmettre explicitement l’ adresse du premier élément du tableau. Vous ne faites donc que passer un pointeur simple, puis vous calculez manuellement le décalage approprié:

 void MyFunction(int *arr, int row, int col) { int i, j; for (i = 0; i < row; i++) for (j = 0; j < col; j++) printf("%d", a[i*col+j]); } int main(void) { int myarray[3][3] = {{1,2,3},{4,5,6},{7,8,9}}; ... MyFunction(&myarray[0][0], 3, 3); 

Puisque nous passons un simple pointeur sur int , nous ne pouvons pas utiliser un indice double dans MyFunc ; le résultat de arr[i] est un entier et non un pointeur. Nous devons donc calculer le décalage complet dans le tableau lors de l'opération à un indice. Notez que cette astuce ne fonctionnera que pour les tableaux réellement multidimensionnels.

Maintenant, un ** peut indiquer des valeurs organisées dans une structure 2D, mais une structure construite différemment. Par exemple:

 void AnotherFunc(int **arr, int row, int col) { int i, j; for (i = 0; i < row; i++) for (j = 0; j < col; j++) printf("%d", arr[i][j]); } int main(void) { int d0[3] = {1, 2, 3}; int d1[3] = {4, 5, 6}; int d2[3] = {7, 8, 9}; int *a[3] = {d0, d1, d2}; AnotherFunc(a, 3, 3); ... } 

Selon la règle ci-dessus, lorsque les expressions d0 , d1 et d2 apparaissent dans l'initialiseur pour a , leurs types sont tous convertis de "tableau à 3 éléments d' int " en "pointeur sur int ". De même, lorsque l'expression a apparaît dans l'appel à AnotherFunc , son type est converti de "tableau de pointeur à 3 éléments en int " en "pointeur en pointeur".

Notez que dans AnotherFunc nous AnotherFunc deux dimensions au lieu de calculer l'offset comme nous l'avons fait dans MyFunc . En effet, a est un tableau de valeurs de pointeur . L'expression arr[i] nous obtient la ième valeur de pointeur décalée par rapport à l'emplacement arr ; on trouve alors la jième valeur entière décalée par rapport à cette valeur de pointeur.

Le tableau suivant peut aider - il montre les types de diverses expressions de tableau et leur désintégration en fonction de leurs déclarations ( T (*)[N] est un type de pointeur, pas un type de tableau, donc il ne se désintègre pas):

 Type d'expression de déclaration converti implicitement (Decays) en
 ----------- ---------- ---- ------------------------- -------
      T a [N] a T [N] T *
                                & a T (*) [N]
                                *à
                              un [i] t

   T [M] [N] a T [M] [N] T (*) [N]
                                & a T (*) [M] [N] 
                                * a T [N] T *
                              a [i] T [N] T *
                             & a [i] T (*) [N] 
                             * a [i] T
                           a [i] [j] T

 T [L] [M] [N] a T [L] [M] [N] T (*) [M] [N]
                                & a T (*) [L] [M] [N]
                                * a T [M] [N] T (*) [N]
                              a [i] T [M] [N] T (*) [N]
                             & a [i] T (*) [M] [N]
                             * a [i] T [N] T *
                           a [i] [j] T [N] T *
                          & a [i] [j] T (*) [N]
                          * a [i] [j] T 
                        a [i] [j] [k] T

Le modèle pour les tableaux de dimension supérieure doit être clair.

Edit: Voici ma tentative de réponse plus précise telle que demandée et basée sur votre nouveau code d’exemple:

Indépendamment des dimensions du tableau, ce que vous passez est un “pointeur sur un tableau” – ce n’est qu’un seul pointeur, bien que le type du pointeur puisse varier.

Dans votre premier exemple, int array[6] est un tableau de 6 éléments int . Passing array transmet un pointeur au premier élément, qui est un int , d’où le type de paramètre est int * , qui peut être écrit de manière équivalente sous la forme int [] .

Dans votre deuxième exemple, int array[3][3] est un tableau de 3 lignes (éléments), chacune contenant 3 int s. Passer array passe un pointeur au premier élément, qui est un tableau de 3 int s . Par conséquent, le type est int (*)[3] – un pointeur sur un tableau de 3 éléments, qui peuvent être écrits de manière équivalente sous la forme int [][3] .

J’espère que vous voyez la différence maintenant. Lorsque vous passez un int ** , il s’agit en fait d’un pointeur sur un tableau d’ int * s et NON d’ un pointeur sur un tableau 2D.

Un exemple pour un int ** réel pourrait ressembler à ceci:

 int a[3] = { 1, 2, 3 }; int b[3] = { 4, 5, 6 }; int c[3] = { 7, 8, 9 }; int *array[3] = { a, b, c }; 

Ici, array est un tableau de 3 int * s, et le passer en argument donnerait un int ** .


Réponse originale:

Votre premier exemple n’est pas vraiment un tableau 2D, bien qu’il soit utilisé de la même manière. Là, vous créez le nombre ROWS de pointeurs char * , chacun d’entre eux COLS vers un tableau différent de caractères COLS . Il y a deux niveaux d’indirection ici.

Les deuxième et troisième exemples sont en réalité des tableaux 2D, où la mémoire de tous les caractères ROWS * COLS est contiguë. Il n’y a qu’un seul niveau d’indirection ici. Un pointeur sur un tableau 2D n’est pas char ** , mais char (*)[COLS] , vous pouvez donc:

 char (*p)[SIZE] = arr; // use p like arr, eg. p[1][2] 

Les autres l’ont bien résumé. int ** A signifie que A est un pointeur sur un tableau et non une référence à un tableau à deux dimensions. Cependant, cela ne signifie pas qu’il n’est pas utilisable. Comme les données en C sont stockées dans l’ordre des lignes, une fois que vous connaissez la longueur de la ligne, la récupération des données doit être facile.

Parce qu’un pointeur de pointeur n’est pas du même type qu’un pointeur de tableau. Voir les pointeurs sur les pointeurs et les tableaux de pointeurs pour plus de détails.

En outre, cela a quelques bonnes informations: http://c-faq.com/aryptr/index.html

Le premier exemple est possible car les tableaux dégénèrent en pointeurs lorsqu’ils sont transmis en tant que parameters de fonction.

Le deuxième exemple ne fonctionne pas car int[3][3] dégénère en int (*)[3] et non en un double pointeur int ** . C’est le cas, car les tableaux 2D sont contigus en mémoire et sans cette information, le compilateur ne saurait pas comment accéder aux éléments après la première ligne. Considérons une simple grid de nombres:

 1 2 6 0 7 9 

Si nous int nums[6] ces nombres dans un tableau int nums[6] , comment pourrions-nous indexer dans le tableau pour accéder à l’élément 7? Par 1 * 3 + 1 , bien sûr, ou plus généralement, row * num-columns + column . Pour accéder aux éléments situés après la première ligne, vous devez savoir combien de colonnes la grid contient.

Lorsque vous stockez les nombres sous la forme nums[2][3] , le compilateur utilise exactement la même row * num-columns + column arithmétique de row * num-columns + column que vous le faites manuellement avec un tableau 1D, elle est simplement masquée du programmeur. Par conséquent, vous devez indiquer le nombre de colonnes lors du passage d’un tableau 2D pour que le compilateur puisse effectuer cette opération arithmétique.

Dans de nombreuses autres langues, les tableaux contiennent des informations sur leur taille, éliminant ainsi la nécessité de spécifier manuellement les dimensions lors du passage de tableaux multidimensionnels à des fonctions.

Peut-être pouvons-nous nous attendre à une question plus “pertinente” si vous souhaitez une réponse plus précise. Votre idée a deux problèmes:

  1. un tableau 2D int A[3][3] lorsqu’il est utilisé dans une expression, se décompose en l’adresse de son premier élément, ainsi en un pointeur de type int (*)[3] . Pour pouvoir passer le tableau, vous devez utiliser &A[0][0] pour obtenir un pointeur sur le premier membre “interne”.
  2. Dans votre fonction, l’opération A[i][j] ne peut pas être effectuée car votre compilateur ne dispose d’aucune information sur la longueur de la ligne.

Ce code pose deux problèmes principaux.

 MyFunction(int **array, int row, int col); 

La première est que int **array est le type incorrect à utiliser. Ceci est un pointeur sur un pointeur, alors que

 int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; 

est un tableau multidimensionnel. La mémoire qui constitue ce tableau multidimensionnel est constituée d’un seul bloc et le décalage depuis le début de celui-ci par rapport à n’importe quel élément de ce tableau est calculé en fonction de la connaissance de la taille d’une ligne de ce tableau.

 int *A[99]; 

Ceci est un tableau de pointeurs sur des entiers. Les entiers pointés peuvent être le premier de plusieurs entiers en mémoire, ce qui signifie qu’ils pointent en réalité sur des tableaux d’entiers.

Dans de nombreuses circonstances, lorsque vous utilisez le nom d’un tableau dans un programme, il se traduit par un pointeur vers le début du tableau. Si tu le dis:

 int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; printf("%p %p %p\n", array, array[0], &(array[0][0]) ); 

Vous devriez obtenir la même adresse imprimée 3 fois car ils font tous référence à la même adresse, mais leurs types ne sont pas les mêmes. Les types de données des deux derniers sont similaires et compatibles à de nombreuses fins, puisque array[0] serait traité comme un pointeur sur le premier élément de la première ligne de array et que cette ligne est un tableau à elle seule.

Si tu le dis:

 int **A; 

Vous dites qu’il y a un pointeur sur un pointeur sur un int . Bien que A[2][4] soit une expression valide, ce n’est pas un tableau multidimensionnel de la même manière que:

 int B[3][3]; 

Si vous dites A[1] cela équivaut à un int * semblable à B[1] , sauf que vous pouvez dire A[1] = (int *)0x4444; , mais si vous avez dit B[1] = (int *)0x4444; vous obtiendrez une erreur du compilateur car B[1] est en fait une valeur calculée, pas une variable. Avec B il n’ya pas de tableau de variables int * – juste quelques calculs basés sur la taille de la ligne et l’adresse du tout premier membre du tableau.

Ce code doit faire quelque chose de similaire à ce que vous vouliez (certaines modifications de la mise en forme de la sortie pour plus de lisibilité). Notez comment la valeur d’index dans l’instruction d’impression est modifiée.

 MyFunction(int *array, int row, int col) { int x = 0; for(int i=0 ; i