Cet idiome sort naturellement de l'allocation de tableau 1D. Commençons par allouer un tableau 1D d'un type arbitraire T
:
T *p = malloc( sizeof *p * N );
Simple, non? L' expression *p
a un type T
, donc sizeof *p
donne le même résultat que sizeof (T)
, donc nous N
allouons suffisamment d'espace pour un tableau -element de T
. Cela est vrai pour tout typeT
.
Maintenant, remplaçons T
par un type de tableau comme R [10]
. Alors notre allocation devient
R (*p)[10] = malloc( sizeof *p * N);
La sémantique ici est exactement la même que celle de la méthode d'allocation 1D; tout ce qui a changé est le type de p
. Au lieu de T *
, c'est maintenant R (*)[10]
. L'expression *p
a un type T
qui est type R [10]
, donc sizeof *p
est équivalent à sizeof (T)
qui est équivalent à sizeof (R [10])
. Nous allouons donc suffisamment d'espace pour un tableau N
par 10
élément de R
.
Nous pouvons aller encore plus loin si nous le voulons; suppose R
est lui-même un type de tableau int [5]
. Remplacez cela R
et nous obtenons
int (*p)[10][5] = malloc( sizeof *p * N);
Même accord - sizeof *p
est le même que sizeof (int [10][5])
, et nous finissons par allouer un morceau de mémoire contigu assez grand pour contenir un N
par 10
par 5
tableau de int
.
Voilà donc le côté allocation; qu'en est-il du côté accès?
N'oubliez pas que l' []
opération d'indice est définie en termes d'arithmétique de pointeur: a[i]
est définie comme *(a + i)
1 . Ainsi, l'opérateur d'indice déréférence []
implicitement un pointeur. Si p
est un pointeur vers T
, vous pouvez accéder à la valeur pointée soit en déréférençant explicitement avec l' *
opérateur unaire :
T x = *p;
ou en utilisant l' []
opérateur indice:
T x = p[0]; // identical to *p
Ainsi, si p
pointe sur le premier élément d'un tableau , vous pouvez accéder à n'importe quel élément de ce tableau en utilisant un indice sur le pointeur p
:
T arr[N];
T *p = arr; // expression arr "decays" from type T [N] to T *
...
T x = p[i]; // access the i'th element of arr through pointer p
Maintenant, refaisons notre opération de substitution et remplaçons T
par le type de tableau R [10]
:
R arr[N][10];
R (*p)[10] = arr; // expression arr "decays" from type R [N][10] to R (*)[10]
...
R x = (*p)[i];
Une différence immédiatement apparente; nous déréférencons explicitement p
avant d'appliquer l'opérateur indice. Nous ne voulons pas souscrire p
, nous voulons indiquer ce qui p
pointe vers (dans ce cas, le tableau arr[0]
). Étant donné que l'unaire *
a une priorité inférieure à l' []
opérateur indice , nous devons utiliser des parenthèses pour regrouper explicitement p
avec *
. Mais rappelez-vous d'en haut que *p
c'est la même chose que p[0]
, donc nous pouvons le remplacer par
R x = (p[0])[i];
ou juste
R x = p[0][i];
Ainsi, si p
pointe vers un tableau 2D, nous pouvons indexer ce tableau p
comme suit:
R x = p[i][j]; // access the i'th element of arr through pointer p;
// each arr[i] is a 10-element array of R
Prenant cela à la même conclusion que ci-dessus et en remplaçant R
par int [5]
:
int arr[N][10][5];
int (*p)[10][5]; // expression arr "decays" from type int [N][5][10] to int (*)[10][5]
...
int x = p[i][j][k];
Cela fonctionne exactement de la même manière s'il p
pointe vers un tableau normal ou s'il pointe vers la mémoire allouée via malloc
.
Cet idiome présente les avantages suivants:
- C'est simple - juste une ligne de code, par opposition à la méthode d'allocation fragmentaire
T **arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( size_t i = 0; i < N; i++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
}
}
- Toutes les lignes du tableau alloué sont * contiguës *, ce qui n'est pas le cas avec la méthode d'allocation fragmentaire ci-dessus;
- Désallouer le tableau est tout aussi simple avec un seul appel à
free
. Encore une fois, ce n'est pas vrai avec la méthode d'allocation au coup par coup, où vous devez désallouer chacun arr[i]
avant de pouvoir désallouer arr
.
Parfois, la méthode d'allocation fragmentaire est préférable, par exemple lorsque votre tas est gravement fragmenté et que vous ne pouvez pas allouer votre mémoire en tant que bloc contigu, ou que vous souhaitez allouer un tableau "en dents de scie" où chaque ligne peut avoir une longueur différente. Mais en général, c'est la meilleure façon de procéder.
1. N'oubliez pas que les tableaux ne sont pas des pointeurs - au lieu de cela, les expressions de tableau sont converties en expressions de pointeur si nécessaire.