Déclaration
Un tableau de tableaux (appelé tableau à plusieurs dimensions) se déclare par précision de la taille de chaque "dimension". Si par exemple je souhaite déclarer un tableau "tridimensionnel" alors je ferais ainsi :
Code : C | int t[taille1][taille2][taille3];
|
'taille1', 'taille2' et 'taille3' sont les tailles de chaque dimension. Autrement dit,
't' est un tableau de
taille1 tableaux de
taille2 tableaux de
taille3 int
s.
Utilisation
Initialisation
L'initialisation de ce type de tableaux n'est pas très différente de celle d'un tableau à une dimension :
Exemple 1 :
Code : C | int tableau1[2][3] = {{1,8,9},{0,6,4}};
|
Exemple 2 :
Code : C | int tableau1[2][3][2] = {{ {1,8} , {0,6} , {0,0} },
{ {31,52} , {4,8} , {11,5}}
};
|
Ou en ne précisant pas la première dimension :
Exemple :
Code : C | int tableau1[][3] = {{1,8,9},{0,6,4},{5,3,7},{2,2,2}};
|
Ainsi on a créé un tableau de 4x3 (équivalent à
int tableau[4][3]
), et initialisé ainsi :
Ou en n'initialisant que quelques cases du tableau :
Exemple :
Code : C | int tableau1[][3] = {{1,9},{0,4},{5,3,7},{2,2,2}};
|
Les cases restantes de chaque ligne seront donc initialisées à 0.
Ou pour initialiser toutes les cases à 0 :
Exemple :
Code : C | int tableau1[][3] = {{0},{0},{0},{0}};
|
Attention :
Code : C | int tableau1[][3] = {{0}};
|
Ceci est équivalent à
int tableau1[1][3]
et non pas
int tableau1[4][3]
!
Comme expliqué dans la partie "tableaux unidimensionnels", on pourrait également initialiser certaines cases de notre tableau; ceci s'applique bien évidemment à des tableaux multidimensionnels. Voici quelques exemples, je vous propose de les faire sous forme d'exercice pour voire si vous avez bien compris.
Exemple 1 :
Code : C | int t[4][5] = {{1,[3]=5,6},
[2]={[3]1},
{2,4,[4]=10}};
|
Secret (cliquez pour afficher)
| 1 |
0 |
0 |
5 |
6 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
1 |
0 |
| 2 |
4 |
0 |
0 |
10 |
La première ligne a été initialisée suivant la règle que nous avons vu pour un tableau à une dimension (si vous ne vous en rappelez pas vous pouvez relire cette partie.
La deuxième ligne n'a pas été initialisée manuellement car nous avons sauté cette ligne pour aller directement à celle d'indice 2 ([2]={...}).
Naturellement l'initialisateur suivant est utilisé pour initialiser la ligne suivante donc celle d'indice 3.
Exemple 2 :
Code : C | int t[4][5] = {[3]={1,[3]=5,6},
[0]={[3]=1},
{2,4,[4]=10}};
|
Secret (cliquez pour afficher)
| 0 |
0 |
0 |
1 |
0 |
| 2 |
4 |
0 |
0 |
10 |
| 0 |
0 |
0 |
0 |
0 |
| 1 |
0 |
0 |
5 |
6 |
Exemple 3 :
Code : C | int t[][5] = { [1]={1,[3]=5,6},
[2]={7,9,[1]=1,5},
{2,4,[4]=10},
{0,7,5,3,8,4,7},
{0}};
|
Secret (cliquez pour afficher)
Il se peut que vous ayez un warning vous indiquant que vous avez dépassé la taille pour l'initialisateur {0,7,5,3,8,4,7}.
| 0 |
0 |
0 |
0 |
0 |
| 1 |
0 |
0 |
5 |
6 |
| 7 |
1 |
5 |
0 |
0 |
| 2 |
4 |
0 |
0 |
10 |
| 0 |
7 |
5 |
3 |
8 |
| 0 |
0 |
0 |
0 |
0 |
Exemple 4 :
Code : C
Secret (cliquez pour afficher)
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
Exemple 5 :
Code : C | int t[][5] = {[1]=1,[3]=5,6,2,4,[4]=10,0,7,5,3,8,4,7,[18]=7,9,[23]=13};
|
Secret (cliquez pour afficher)
| 0 |
0 |
0 |
0 |
0 |
| 1 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 5 |
6 |
2 |
4 |
0 |
| 10 |
0 |
7 |
5 |
3 |
| 8 |
4 |
7 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 7 |
9 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
| 13 |
0 |
0 |
0 |
0 |
Parcours
Le parcours d'un tableau à plusieurs dimensions s'effectue en précisant l'indice pour chaque dimension.
Pour un exemple de tableau de trois dimensions, et à l'aide de trois boucles imbriquées, on arriverait à accéder à toutes les cases de notre tableau.
Exemple :
Code : C | int tableau[taille1][taille2][taille3];
int i,j,k;
for(i=0 ; i < taille1 ; i++){ //Première dimension
for(j=0 ; j < taille2 ; j++){ //Deuxième dimension
for(k=0 ; k < taille3 ; k++){ //troisième dimension
tableau[i][j][k] = 0;
}
}
}
|
Les pointeurs sur tableaux
Vous pensez peut être au double pointeur (int **)

, non ce n'est pas de cela qu'il s'agit.
La déclaration d'un pointeur sur tableau s'effectue en utilisant des parenthèses, l'astérisque '*' et en définissant la taille du tableau sur lequel on souhaite pointer. Notez que les parenthèses sont très importantes car en leur absence, ce sera un tableau de pointeurs qu'on aura déclaré, ce qui n'est pas la même chose.
Exemple :
Code : C
Dans cet exemple, il s'agit d'une déclaration d'un pointeur sur tableaux de 4 entiers.
L'erreur à ne pas faire :
Code : C
Ici il ne s'agit pas d'un pointeur sur tableau de 4, mais un tableau de 4 pointeurs sur int (dont l'explication est dans la partie suivante).
Utilité des pointeurs sur tableaux
C'est un moyen très pratique pour déclarer un tableau à deux dimensions

. Sachant qu'une dimension est déjà pré allouée, il ne reste plus qu'à allouer la deuxième dimension.
Exemple :
Code : C | int (*tableau)[4];
tableau = malloc(5 * sizeof(*tableau));
|
Ainsi on aura déclaré un tableau de 5 tableaux de 4 entiers chacun (équivalent à
int tableau[5][4];
).
Libération de mémoire allouée
Pour libérer la mémoire, on doit utiliser la fonction free toujours, et lui donner en paramètre le pointeur sur l'espace alloué à l'aide de malloc :
Code : C | int (*tableau)[4];
//----Allocation-----
tableau = malloc(5 * sizeof(*tableau));
if(tableau == NULL){
//Notifier l'erreur
exit(EXIT_FAILURE);
}
//----Libération en cas d'allocation réussie-----
free(tableau);
|
La deuxième dimension sera libérée automatiquement donc pas besoin de free pour le faire.
Ainsi on remarque que ce type est beaucoup plus rapide d'utilisation (en terme d'allocation/libération de mémoire) du fait qu'il ne nécessite pas de boucles.
Les tableaux de pointeurs
Un tableau de pointeurs, est un outil pour ranger un ensemble de pointeurs sur différentes variables.
Il peut également servir pour déclarer un tableau à deux dimensions, en allouant plusieurs espaces et stockant leurs pointeurs dans notre tableau.
Exemple de déclaration :
Code : C
Exemple d'utilisation pour déclarer un tableau à deux dimensions :
Code : C | int * tableauDePtr[5];
int i;
for(i=0 ; i < 5 ; i++){
tableauDePtr[i] = malloc(4 * sizeof(tableau[0]));
}
|
Ainsi on aura créé un tableau de 5 tableaux de 4 entiers chacun (équivalent à
int tableau[5][4];
).
Libération de mémoire allouée
La libération de mémoire allouée doit se faire en parcourant le tableau, et en allouant pointeur par pointeur dans ce tableau :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | int i;
int * tableauDePtr[5];
//---Allocation-----
for(i=0 ; i < 5 ; i++){
tableauDePtr[i] = malloc(4 * sizeof(tableau[0]));
if(tableauDePtr[i] == NULL){ //En cas d'erreur d'allocation
//N'oubliez pas de notifier l'erreur
for(i=i-1 ; i >= 0 ; i--) //Libération de l'espace déjà alloué
free(tableauDePtr[i]);
exit(EXIT_FAILURE);
}
}
//---Libération en cas d'allocation réussie-----
for(i=0 ; i < 5 ; i++){
free(tableauDePtr[i]);
}
|
L'erreur à ne pas faire :
Code : C
tableauDePtr est un type automatique, donc sera libéré automatiquement à la fin de la fonction dans laquelle il est déclaré. Faites donc attention à ne pas le confondre avec le type pointeur sur tableaux qu'on a vu précédemment.
Allocation dynamique
Cette partie je la qualifie étant la plus dure à suivre, donc mettez vos ceintures

.
L'allocation dynamique d'un dit "tableau" à plusieurs dimensions, s'effectue en allouant les dimensions une par une. Si je prends l'exemple d'un tableau tridimensionnel, il faudrait un triple pointeur pour y arriver. Donc veillez à avoir le même nombre d'astérisques '*' dans la déclaration de pointeur, que le nombre de dimensions de votre tableau.
Si je prends un exemple de tableau à 3 dimensions que je souhaite allouer dynamiquement, j'utiliserais un pointeur déclaré ainsi :
Code : C | int ***ptr; //3 étoiles pour 3 dimensions
|
Et un tableau à deux dimensions nécessiterait un double pointeur :
Code : C | int **ptr; //2 étoiles pour 2 dimensions
|
Tableaux bidimensionnels
Ce type de tableaux, est un simple tableau de pointeurs, chacun de ces pointeurs va pointer sur un espace représentant un tableau à une dimension, une petite image est la bienvenue je pense

:
L'allocation doit se faire en respectant la démarche suivante :
- 1- Allocation de la première dimension (ptr) :
Il s'agit du tableau de pointeurs qui contiendra plusieurs pointeurs qui à leur tour pointent sur des espaces alloués sous forme de tableaux.
Code : C | int **ptr;
ptr = malloc(taille1 * sizeof(*ptr)); //On alloue 'taille1' pointeurs.
if(ptr == NULL)
//Ne pas oublier de notifier l'erreur et de quitter le programme.
|
- 2- Allocation de la deuxième dimension (*ptr) :
Il s'agit des différents tableaux appelés "tableaux de valeurs" sur l'image ci-dessus.
Code : C | int i;
for(i=0 ; i < taille1 ; i++){
ptr[i] = malloc(taille2 * sizeof(*(ptr[i]))); //On alloue des tableaux de 'taille2' variables.
if(ptr[i] == NULL){
//Il faut libérer la mémoire déjà allouée
//Ne pas oublier de notifier l'erreur et de quitter le programme.
}
}
|
Ainsi on obtient le code suivant :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | int i , taille1 = 2 , taille2 = 3;
int **ptr;
ptr = malloc(taille1 * sizeof(*ptr)); //On alloue 'taille1' pointeurs.
if(ptr == NULL)
//Ne pas oublier de notifier l'erreur et de quitter le programme.
for(i=0 ; i < taille1 ; i++){
ptr[i] = malloc(taille2 * sizeof(**ptr) ); //On alloue des tableaux de 'taille2' variables.
if(ptr[i] == NULL){ //En cas d'erreur d'allocation
//Il faut libérer la mémoire déjà allouée
//Ne pas oublier de notifier l'erreur et de quitter le programme.
}
}
//notre tableau ptr[2][3] est maintenant utilisable...
|
Quant à la libération de mémoire elle se fait suivant l'ordre inverse à l'allocation :
- 1- Libération de la deuxième dimension (*ptr) :
En utilisant une boucle, on doit donc libérer les différents tableaux à une dimension qu'on a alloués :
Code : C | int i;
for(i=0 ; i < taille1 ; i++){
free(ptr[i]);
}
|
- 2- Libération de la première dimension (ptr) :
Code : C
On obtient donc le code suivant :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12 | int i;
//Après allocation....
//--------La libération---------
for(i=0 ; i < taille1 ; i++){
free(ptr[i]);
}
free(ptr);
ptr = NULL; //Ceci est par mesure de sécurité (ce n'est donc pas obligatoire).
//--------------------------------
|
Cette libération n'est utilisable que si l'allocation s'est bien déroulé (sans erreurs), Que faire donc en cas d'erreur d'allocation

?
Et bien il s'agit des
if( ptr[i] == NULL)
que vous avez peut être remarquées dans le code d'allocation

. On va maintenant essayer de les remplir avec ce qu'il faut.
- 1- Première dimension :
Code : C | int **ptr;
ptr = malloc(taille1 * sizeof(*ptr)); //On alloue 'taille1' pointeurs.
if(ptr == NULL)
//Pas de libération nécessaire à ce niveau, il suffirait donc de notifier l'erreur et de fermer le programme (par utilisation de exit(-1) par exemple).
|
- 2- Deuxième dimension :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12 | int i;
for(i=0 ; i < taille1 ; i++){
ptr[i] = malloc(taille2 * sizeof(*(ptr[i]))); //On alloue des tableaux de 'taille2' variables.
if(ptr[i] == NULL){
for(i = i-1 ; i >= 0 ; i--) //On parcourt la boucle dans l'ordre inverse pour libérer ce qui a déjà été alloué
free(ptr[i]);
free(ptr); //On libère la première dimension.
//Ne pas oublier de notifier l'erreur et de quitter le programme (en utilisant exit(-2) par exemple).
}
}
|
Tableaux tridimensionnels
Passant maintenant à un tableau à 3 dimensions

. Il s'agit d'un tableau de pointeurs sur pointeurs, chacun d'eux va pointer sur un pointeur qui lui pointe sur un tableau

bref, trop de pointeurs et de blabla, une image serait donc plus explicite :
L'allocation dynamique de ce tableau, sera divisée en 3 étapes qui doivent correspondre à l'ordre suivant :
- 1- Allocation de la première dimension, représentée par ptr : Par analogie aux tableaux automatiques, ce sera celle qui se trouve entre crochets ([]) à gauche.
Code : C | int ***ptr;
ptr = malloc(taille1 * sizeof(*ptr));
|
- 2- allocation de la deuxième dimension, représentée par *ptr : Par analogie aux tableaux automatiques, ce sera celle qui se trouve entre crochets ([]) au milieu.
Code : C | for(i=0 ; i < taille1 ; i++){
ptr[i] = malloc(taille2 * sizeof(**ptr));
}
|
- 3- allocation de la troisième dimension, représentée par **ptr : Par analogie aux tableaux automatiques, ce sera celle qui se trouve entre crochets ([]) à droite.
Code : C | for(i=0 ; i < taille1 ; i++){
for(j=0 ; j < taille2 ; j++){
ptr[i][j] = malloc(taille3 * sizeof(***ptr));
}
}
|
Nous nous retrouvons donc avec le code suivant :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | int ***ptr;
int i,j;
ptr = malloc(taille1 * sizeof(*ptr));
if(ptr == NULL) //Pas de libération de mémoire à ce niveau
return -1; //Exemple de code d'erreur
for(i=0 ; i < taille1 ; i++){
ptr[i] = malloc(taille2 * sizeof(**ptr));
if( ptr[i] == NULL) //Pensez à libérer la mémoire déjà allouée et fermer le programme.
return -1; //Exemple de code d'erreur
}
for(i=0 ; i < taille1 ; i++){
for(j=0 ; j < taille2 ; j++){
ptr[i][j] = malloc(taille3 * sizeof(***ptr));
if(ptr[i][j] == NULL) //Pensez à libérer correctement la mémoire déjà allouée
return -1; //Exemple de code d'erreur
}
}
|
Pour un tableau de dimensions supérieurs, il faut procéder de la même manière, en allouant les dimensions une à une, et en respectant l'ordre des allocations.
Pour la libération de mémoire déjà allouée, il faut que cela soit fait dans l'ordre inverse à celui de l'allocation. Pour le même exemple présenté ci-dessus on procèderait ainsi :
- 1- Libération de l'espace **ptr.
Code : C | for(i=0 ; i < tailleDejaAllouee1 ; i++){
for(j=0 ; j < tailleDejaAllouee2 ; j++){
free(ptr[i][j]);
}
}
|
- 2- Libération de l'espace *ptr.
Code : C | for(i=0 ; i < tailleDejaAllouee1 ; i++){
free(ptr[i]);
}
|
- 3- Libération de l'espace ptr.
Code : C
Le code de libération est donc comme ceci :
Code : C | for(i=0 ; i < tailleDejaAllouee1 ; i++){
for(j=0 ; j < tailleDejaAllouee2 ; j++){
free(ptr[i][j]);
}
free(ptr[i]);
}
free(ptr);
|
Attention :
Cette méthode de libération n'est valable que si l'allocation s'est bien déroulée.
Voici un code montrant la méthode de libération de mémoire si l'allocation échoue avant d'allouer le tableau entièrement :
Secret (cliquez pour afficher)Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 | #include <stdlib.h>
void * my_free(int***,int,int);
void * my_free(int ***ptr,int tailleDejaAllouee1,int tailleDejaAllouee2){
int i,j;
for(i=0 ; i < tailleDejaAllouee1 ; i++){
for(j=0 ; j < tailleDejaAllouee2 ; j++){
free(ptr[i][j]);
}
free(ptr[i]);
}
free(ptr);
return NULL;
}
int main(void)
{
/*---------------------------------------------------*/
int taille1 = 2, taille2 = 2, taille3 = 2;
int ***ptr;
int i,j;
/*-----------------Allocation------------------------*/
ptr = malloc(taille1 * sizeof(*ptr));
if(ptr == NULL)
return -1;
for(i=0 ; i < taille1 ; i++){
ptr[i] = malloc(taille2 * sizeof(**ptr));
if( ptr[i] == NULL){ //Erreur d'allocation
for(--i ; i>=0 ; i--) //On libère l'espace déjà alloué
free(ptr[i]);
free(ptr);
return -1; //Fin du programme
}
}
for(i=0 ; i < taille1 ; i++){
for(j=0 ; j < taille2 ; j++){
ptr[i][j] = malloc(taille3 * sizeof(***ptr));
if(ptr[i][j] == NULL){ //Erreur d'allocation
for(--j ; j >= 0 ; j--) //On libère l'espace déjà alloué
free(ptr[i][j]);
free(ptr[i]);
my_free(ptr , i , taille2);
return -2; //Fin du programme
}
}
}
/*---------------Libération--------------------------*/
ptr = my_free(ptr,taille1,taille2); //On libère la mémoire et on met la valeur de ptr à NULL
/*---------------------------------------------------*/
return 0;
}
|
Attention :
On pourrait optimiser nos boucles d'allocation comme ceci :
Secret (cliquez pour afficher)Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | int ***ptr;
int i,j;
ptr = malloc(taille1 * sizeof(*ptr));
if(ptr == NULL) //Pas de libération de mémoire à ce niveau
return -1; //Exemple de code d'erreur
for(i=0 ; i < taille1 ; i++){
ptr[i] = malloc(taille2 * sizeof(**ptr));
if( ptr[i] == NULL) //Pensez à libérer la mémoire déjà allouée et fermer le programme.
return -1; //Exemple de code d'erreur
for(j=0 ; j < taille2 ; j++){
ptr[i][j] = malloc(taille3 * sizeof(***ptr));
if(ptr[i][j] == NULL) //Pensez à libérer correctement la mémoire déjà allouée
return -1; //Exemple de code d'erreur
}
}
|
Cependant le problème se posera dans la libération de mémoire déjà allouée qui sera beaucoup plus compliquée

. Si vous n'êtes pas convaincu de la complexité que cela peut engendrer, essayer de le faire, et faîtes vous corrigé par une personne confirmée.
Fonctions : mettre un tableau en argument
Si vous pensez à utiliser le double pointeur (int **), non il ne s'agit pas de cela

.
L'erreur à ne pas faire :
Code : C | void ma_fonction(int ** tableau2D){
//....
tableau2D[i][j] = 10;
}
int main(void){
int tableau2D[10][5];
ma_fonction(tableau2D);
return 0;
}
|
Si le tableau est alloué automatiquement, il ne faut pas l'envoyer à une fonction attendant un pointeur sur pointeur (cf : code ci-dessus).
Les méthodes correctes correspondent globalement aux différentes déclarations d'un tableau telles que nous l'avons vu plus haut

.
Méthode classique
Lors de la déclaration des arguments d'une fonction, le type tableau à plusieurs dimensions est désigné par des crochets [], avec ou sans taille.
Code : C | void ma_fonction(int tableau[taille1][taille2][taille3]){
//Corps de la fonction...
}
|
Exemple de déclaration correcte :
Code : C | void ma_fonction(int tableau[][taille2][taille3]){
//Corps de la fonction...
}
|
Et l'appel sera par simple envoi du nom du tableau, c'est valable pour les deux cas, à savoir avec ou sans précision de la taille de la première dimension :
Code : C | int tableau[taille1][taille2][taille3]; //La déclaration du tableau
ma_fonction(tableau); //L'appel à la fonction avec passage d'un tableau en argument.
|
Utiliser un pointeur sur tableau
Exemple :
Code : C | int fonction(int (*matrice)[3]){
//......
matrice[0][2] = 15;
}
|
Dans cet exemple
matrice est un pointeur sur tableau de 3
int
.
Il permettra de réceptionner la matrice envoyée sous forme de pointeur sur le premier élément (en l'occurrence, pointeur sur tableau de trois entiers) lors de la l'appel à cette fonction.
Exemple :
Secret (cliquez pour afficher)Code : C | int fonction(int (*matrice)[3]){
//......
matrice[0][2] = 15;
}
int main(void){
int matrice[4][3]; //Notez que la taille 3 correspond à celle spécifiée dans la déclaration de la fonction!
fonction(matrice);
return 0;
}
|
Jusqu'ici, vous avez peut être remarqué que si l'on souhaite parcourir la matrice passée en argument, on serait obligé d'avoir une information sur la taille de la matrice. Vous pensez peut être à utiliser l'opérateur
sizeof
? Alors je vous mets de suite en garde, cela ne fonctionnera pas (du moins pour la première dimension).
Code : C | void fonction(int tab[][3]){
sizeof tab;
}
|
Qui est équivalent à :
Code : C | void fonction(int tab[5][3]){
sizeof tab;
}
|
Qui est équivalent à :
Code : C | void fonction(int (*tab)[3]){
sizeof tab;
}
|
Contrairement à ce qu'on pourrait s'attendre à voir, dans ces trois cas le résultat de l'opérateur
sizeof
ne donnera pas la taille des tableaux déclarés en paramètre, mais celle d'un pointeur (souvent 4 octets). Ce qui démontre (en quelque sorte) que le passage d'un paramètre formel de type 'tableau' n'est pas faisable à partir des déclarations ci-dessus.
Dans le cas général, on préfère passer la taille de notre matrice (ou au moins la taille des dites "lignes") en paramètre à la fonction.
Secret (cliquez pour afficher)Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | void fonction1(int matrice[][3], size_t Nlignes){
sizeof matrice[0] * Nlignes; //Ceci permet de récupérer la taille de toute la matrice.
sizeof *matrice * Nlignes; //équivalente à celle ci-dessus
}
void fonction2(int matrice[10][3], size_t Nlignes){ //La taille 10 ici sera ignorée, donc est inutile. Contrairement à la taille 3 qu'il faudra respecter!
sizeof matrice[0] * Nlignes; //Ceci permet de récupérer la taille de toute la matrice.
sizeof *matrice * Nlignes; //équivalente à celle ci-dessus
}
void fonction3(int (*matrice)[3], size_t Nlignes){
sizeof matrice[0] * Nlignes; //Ceci permet de récupérer la taille de toute la matrice.
sizeof *matrice * Nlignes; //équivalente à celle ci-dessus
}
int main (void){
int matrice[4][3];
fonction1(matrice,4);
fonction2(matrice,4);
fonction3(matrice,4);
}
|
Ainsi nous disposerons de toutes les informations nécessaires pour parcourir notre matrice à l'aide d'une boucle.
Utilisation d'un double pointeur int **ptr
Oui je vous ai mentit

en vous disant qu'il n'est pas possible de réceptionner une matrice statique à partir d'un double pointeur. Mais cela n'est tout de même pas si simple à manier.
La méthode consisterait à réceptionner l'adresse envoyée lors de l'appel de la fonction, qui représente l'adresse mémoire du premier élément de la première dimension de la matrice. Et à l'aide d'un tableau intermédiaire de pointeurs, ainsi que de la mise en équation des adresses mémoire de toutes les cases de la matrice, on arriverait à manier correctement une matrice 2D.
Je vous propose d'analyser ce code :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | int fonction(int **mat)
{
int i, j, *index[3]; //Index est le tableau de pointeurs qu'on utilisera sous forme de mémoire tampon.
for (i = 0 ; i < 3 ; i++) //On initialise notre tableau intermédiaire 'index'
index[i] = (int *)mat + 3 * i;
for(i = 0 ; i < 3 ; i++)
{
printf("\n");
for(j = 0 ; j < 3 ; j++)
{
printf("%d", index[i][j]);
}
}
printf("\n");
return 0;
}
|
Pour bien comprendre le pourquoi du comment, je vous fais un petit rappel (qui ne sera pas qu'un simple rappel pour certains).
Imaginons que l'on veuille mettre en équation les adresses des cases constituant une matrice, afin de pouvoir les indexer en utilisant uniquement comme données l'adresse du premier élément dans cette matrice ainsi que sa taille (lignes et colonnes).
Avant d'attaquer les explications, je tiens à vous informer/rappeler que le langage C garantie qu'un tableau à deux dimensions sera placé en mémoire selon la disposition
Row-major order.
En quoi cela nous intéresse ?
Cela signifie que seulement à partir de l'adresse du premier élément, et en connaissant la taille d'une matrice, on arriverait à connaître l'adresse de chacune de ses cases.
Exemple :
Cette matrice est ramenée dans la mémoire à la forme :
(disposition Row-major order).
Pour la suite de l'explication, nous allons prendre trois variables de type pointeurs avec les noms
adresse_1,
adresse_2 et
adresse_3, qui sont respectivement les adresses en mémoire des nombres 1, 4 et 7.
Nous allons dans un premier temps récupérer l'adresse de la première case (celle contenant le chiffre 1), puis nous allons nous décaler de trois cases (trois étant le nombre de colonnes de la matrice) pour obtenir les adresses des élément 4 et 7;
Code : C | int matrice[3][3] = {{1,2,3},{4,5,6},{7,8,9}}; //La matrice
int **adresse_matrice = (int**)matrice; //Ceci est pour simuler un passage d'une matrice à une fonction
int *adresse_1, *adresse_2, *adresse_3;
adresse_1 = (int*)adresse_matrice;
adresse_2 = (int*)adresse_matrice + 3;
adresse_3 = (int*)adresse_matrice + 6;
|
Ensuite nous allons regrouper maintenant nos pointeurs adresse_1/2/3 dans un tableau :
Code : C | int matrice[3][3] = {{1,2,3},{4,5,6},{7,8,9}}; //La matrice
int **adresse_matrice = (int**)matrice; //Ceci est pour simuler un passage d'une matrice à une fonction
int *adresse[3]; //Les pointeurs intermédiaires
adresse[0] = (int*)adresse_matrice + (0*3);
adresse[1] = (int*)adresse_matrice + (1*3);
adresse[2] = (int*)adresse_matrice + (2*3);
|
D'où le code de la fonction présenté précédemment.
Secret (cliquez pour afficher)Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | int fonction(int **mat)
{
int i, j, *index[3]; //Index est le tableau de pointeurs qu'on utilisera sous forme de mémoire tampon.
for (i = 0 ; i < 3 ; i++) //On initialise notre tableau intermédiaire 'index'
index[i] = (int *)mat + 3 * i;
for(i = 0 ; i < 3 ; i++)
{
printf("\n");
for(j = 0 ; j < 3 ; j++)
{
printf("%d", index[i][j]);
}
}
printf("\n");
return 0;
}
|
En conclusion
Cette méthode n'est quasiment pas utilisée dans la pratique, de part son caractère complexe, sans oublier qu'elle fait appel à un tableau intermédiaire, et peut poser des problème du fait qu'on fait des affectations aux pointeurs avec types différents.
Je ne vous conseillerai donc pas de l'utiliser dans le cas général; je tenais simplement à vous la présenter à titre informatif.
Utilisation d'un typedef
L'utilisation du typedef n'est qu'un raccourci, pour alléger les déclarations de tableaux.
Exemple :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | typedef int tableau3x3x3[3][3][3];
void ma_fonction1(tableau3x3x3 tab){ //Déclaration d'un argument de type tableau tridimensionnel de 3x3x3
//Corps de la fonction...
printf("%zu\n",sizeof(tab[0][0]));//Pour un test
}
int main(void)
{
tableau3x3x3 tableau; //Déclaration du tableau 3D
ma_fonction1(tableau); //Appel de la fonction en lui passant notre tableau en paramètre
return 0;
}
|
Fonctions : retourner un tableau
Comme nous l'avons vu dans la partie des tableaux unidimensionnels, il est interdit de renvoyer un tableau automatique (alloué statiquement) :
Code : C | int (ma_fonction(void))[2][3][4]{
int tableau[2][3][4];
return tableau;
}
|
Il est donc impératif de procéder à une allocation dynamique, puis de retourner le pointeur sur l'espace alloué dynamiquement (tel que nous l'avons vu avant dans la partie d'allocation dynamique

).
Exemple :
Secret (cliquez pour afficher)Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | int *** fonction_allocation(int taille1, int taille2, int taille3){
int ***ptr;
int i,j;
ptr = malloc(taille1 * sizeof(*ptr));
if(ptr == NULL) //Pas de libération de mémoire à ce niveau
return -1; //Exemple de code d'erreur
for(i=0 ; i < taille1 ; i++){
ptr[i] = malloc(taille2 * sizeof(**ptr));
if( ptr[i] == NULL) //Pensez à libérer la mémoire déjà allouée et fermer le programme.
return -1; //Exemple de code d'erreur
}
for(i=0 ; i < taille1 ; i++){
for(j=0 ; j < taille2 ; j++){
ptr[i][j] = malloc(taille3 * sizeof(***ptr));
if(ptr[i][j] == NULL) //Pensez à libérer correctement la mémoire déjà allouée
return -1; //Exemple de code d'erreur
}
}
return ptr; //retour du pointeur sur l'espace alloué
}
int main(void){
int taille1 = 2, taille2 = 2, taille3 = 2;
int *** ptr = fonction_allocation(taille1,taille2,taille3);
//.....
ptr = my_free(ptr,taille1,taille2); //Cette fonction est décrite dans la partie allocation dynamique
return 0;
}
|