web-dev-qa-db-fra.com

Comment les tableaux multidimensionnels sont-ils formatés en mémoire?

En C, je sais que je peux allouer dynamiquement un tableau à deux dimensions sur le tas en utilisant le code suivant:

int** someNumbers = malloc(arrayRows*sizeof(int*));

for (i = 0; i < arrayRows; i++) {
    someNumbers[i] = malloc(arrayColumns*sizeof(int));
}

Clairement, cela crée en fait un tableau unidimensionnel de pointeurs vers un ensemble de tableaux séparés unidimensionnels d'entiers, et "Le système" peut comprendre ce que je veux dire quand je demande:

someNumbers[4][2];

Mais quand je déclare statiquement un tableau 2D, comme dans la ligne suivante ...:

int someNumbers[ARRAY_ROWS][ARRAY_COLUMNS];

... une structure similaire est-elle créée sur la pile ou est-elle complètement différente? (c’est-à-dire un tableau 1D de pointeurs? Si non, de quoi s'agit-il et comment y trouve-t-on des références?)

De plus, quand j'ai dit "Le système", qu'est-ce qui est réellement responsable de cette découverte? Le noyau? Ou le compilateur C le trie-t-il pendant la compilation?

159
Chris Cooper

Un tableau statique à deux dimensions ressemble à un tableau de tableaux - il est simplement présenté de manière contiguë en mémoire. Les tableaux ne sont pas la même chose que les pointeurs, mais étant donné que vous pouvez souvent les utiliser de manière assez interchangeable, cela peut parfois être déroutant. Le compilateur garde la trace correctement, cependant, ce qui rend tout bien aligné. Vous devez être prudent avec les tableaux 2D statiques comme vous l'avez mentionné, car si vous essayez de passer l'un à une fonction prenant un int ** paramètre, de mauvaises choses vont arriver. Voici un exemple rapide:

int array1[3][2] = {{0, 1}, {2, 3}, {4, 5}};

En mémoire ressemble à ceci:

0 1 2 3 4 5

exactement identique à:

int array2[6] = { 0, 1, 2, 3, 4, 5 };

Mais si vous essayez de passer array1 à cette fonction:

void function1(int **a);

vous recevrez un avertissement (et l'application n'aura pas accès au tableau correctement):

warning: passing argument 1 of ‘function1’ from incompatible pointer type

Parce qu'un tableau 2D n'est pas la même chose que int **. La décomposition automatique d'un tableau en un pointeur n'a pour ainsi dire qu'une profondeur d'un niveau. Vous devez déclarer la fonction comme:

void function2(int a[][2]);

ou

void function2(int a[3][2]);

Pour que tout soit heureux.

Ce même concept s'étend aux n - tableaux multidimensionnels. Tirer parti de ce genre d'activité amusante dans votre application ne le rend généralement que plus difficile à comprendre. Alors soyez prudent là-bas.

125
Carl Norum

La réponse est basée sur l’idée que C n’a pas vraiment a tableaux 2D - il a des tableaux de tableaux. Lorsque vous déclarez ceci:

int someNumbers[4][2];

Vous demandez que someNumbers soit un tableau de 4 éléments, chaque élément de ce tableau étant de type int [2] _ (qui est lui-même un tableau de 2 ints).

L’autre partie du problème est que les tableaux sont toujours disposés de manière contiguë en mémoire. Si vous demandez:

sometype_t array[4];

alors cela ressemblera toujours à ceci:

| sometype_t | sometype_t | sometype_t | sometype_t |

(4 sometype_t objets disposés les uns à côté des autres, sans espace entre eux). Donc dans votre someNumbers array-of-array, cela ressemblera à ceci:

| int [2]    | int [2]    | int [2]    | int [2]    |

Et chaque int [2] élément est lui-même un tableau, qui ressemble à ceci:

| int        | int        |

Donc dans l’ensemble, vous obtenez ceci:

| int | int  | int | int  | int | int  | int | int  |
76
caf
unsigned char MultiArray[5][2]={{0,1},{2,3},{4,5},{6,7},{8,9}};

en mémoire est égal à:

unsigned char SingleArray[10]={0,1,2,3,4,5,6,7,8,9};
26
kanghai

En réponse à votre aussi: Les deux, même si le compilateur fait le gros du travail.

Dans le cas de tableaux alloués de manière statique, "The System" sera le compilateur. Il réservera la mémoire comme il le ferait pour toute variable de pile.

Dans le cas du tableau malloc'd, "The System" sera l'implémenteur de malloc (le noyau en général). Tout ce que le compilateur allouera est le pointeur de base.

Le compilateur va toujours gérer le type tel qu'il est déclaré, à l'exception de l'exemple donné par Carl, dans lequel il peut déterminer un usage interchangeable. C'est pourquoi si vous transmettez un [] [] à une fonction, il faut supposer qu'il s'agit d'un appartement alloué de manière statique, où ** est supposé être pointeur à pointeur.

5
Jon L

Pour accéder à un tableau 2D particulier, considérez la carte mémoire pour une déclaration de tableau comme indiqué dans le code ci-dessous:

    0  1
a[0]0  1
a[1]2  3

Pour accéder à chaque élément, il suffit de transmettre le tableau qui vous intéresse en tant que paramètres de la fonction. Ensuite, utilisez offset for column pour accéder à chaque élément individuellement.

int a[2][2] ={{0,1},{2,3}};

void f1(int *ptr);

void f1(int *ptr)
{
    int a=0;
    int b=0;
    a=ptr[0];
    b=ptr[1];
    printf("%d\n",a);
    printf("%d\n",b);
}

int main()
{
   f1(a[0]);
   f1(a[1]);
    return 0;
}
1
Akshay Immanuel D