Les tableaux
Un tableau est une structure de données qui permet de stocker plusieurs valeurs de même type dans une seule variable.
Ces valeurs sont accessibles via un indice (un int) qui permet de spécifier quelle valeur on souhaite utiliser.
0.Voici un exemple de déclaration et d'utilisation d'un tableau en C :
Dans ce programme, on peut voir la déclaration d'un tableau nommé tableau et contenant 4 valeurs de type int : 1, 2, 3 et 4.
On peut aussi y voir l'affichage du premier élément du tableau grâce à l'expression tableau[0] qui est de type int et la modification du
troisième élément du tableau avec l'instruction tableau[2] = 6;.
Il est important de savoir qu'il n'existe pas de type array en C. Les tableaux sont en réalité des pointeurs vers les premiers éléments des tableaux. Le type de la
variable tableau dans l'exemple précédent est en fait int*. C'est un peu déroutant mais c'est un choix de conception qui date des débuts du langage C dans les années 70
et qui permettait de rendre le compilateur plus simple et le code plus performant car les ressources matérielles étaient limitées à l'époque.
Voici quelques autres exemples d'opérations courantes sur les tableaux en C :
Déclaration d'un tableau de
doublede taille10non initialisé :double tableau[10];
Déclaration d'un tableau de
charde taille5initialisé avec la même valeur'b':char tableau[5] = {'b'}; // équivalent à {'b', 'b', 'b', 'b', 'b'}Déclaration simplifiée d'un tableau de
intinitialisé avec les valeurs10,20,30et40:int tab[] = {10, 20, 30, 40}; // la taille est déduite automatiquement (4 dans ce cas)Récupération de l'adresse mémoire du deuxième élément d'un tableau de
float:float* adresse = &tab[1];
Itération sur les éléments d'un tableau de
floatde taillenpour les afficher :for (int i = 0; i < n; i++) { printf("%f\n", tab[i]); }
Les tableaux de taille dynamique
Dans les exemples précédents, les tableaux avaient une taille fixe connue à la compilation.
Quand cela est possible, c'est la meilleure approche car elle est plus simple et plus performante.
Cependant, il est parfois nécessaire de créer des tableaux dont la taille n'est pas connue à la compilation.
Quand c'est le cas, il n'est plus possible de déclarer un tableau de la manière classique, il faut alors utiliser
les fonctions malloc et free du module stdlib de la bibliothèque standard pour allouer et désallouer
des tableaux. La différence principale est que lors de la déclaration classique, la mémoire est allouée sur la stack, ce
qui est rapide mais qui nécessite une taille connue à la compilation, tandis qu'avec malloc, la mémoire est allouée sur la heap,
ce qui est plus lent mais qui permet d'avoir des tailles dynamiques. Une autre différence est que la mémoire allouée dans la stack est
automatiquement libérée à la fin de l'exécution des fonctions, tandis que la mémoire allouée dans la heap doit être libérée manuellement
avec free pour éviter les fuites de mémoire.
Comme vous pouvez le voir, l'allocation dynamique d'un tableau est un peu plus compliquée que la déclaration classique. Nous allons donc décomposer cet appel à malloc :
(float*)est un cast qui permet de convertir un type en un autre. En réalité, la fonctionmallocn'alloue pas un tableau, elle alloue un bloc de mémoire d'une certaine taille mais sans type particulier et elle renvoie un pointeurvoid*vers ce bloc. Il faut donc convertir le pointeur renvoyé parmallocen pointeur vers le type souhaité (float*dans ce cas) pour interpréter le bloc de mémoire correctement.sizeof(float) * (unsigned int)entree_utilisateurcalcule la taille du bloc de mémoire à allouer.sizeof(float)renvoie la taille en octets d'un seulfloatet cette taille est ensuite multipliée par le nombre d'éléments souhaités dans le tableau (entree_utilisateurdans ce cas, mais qui est casté enunsigned intcarmallocrequiert unintnon-signé) pour obtenir la taille totale du bloc de mémoire à allouer.
Il y a aussi l'instruction free(tab); qui permet de libérer la mémoire allouée dynamiquement pour le tableau tab. Ce n'est pas fait automatiquement pour les tableaux dynamiques
donc pour chaque appel à malloc, il faut faire un appel correspondant à free lorsque le tableau n'est plus nécessaire.
Comportement spéciaux des tableaux avec les fonctions
Étant donné que les tableaux sont en réalité des pointeurs et qu'ils peuvent être stockés dans la stack ou dans la heap, ils ont des comportements un peu particuliers lorsqu'ils sont utilisés avec des fonctions.
Passage par référence
Puisque les tableaux sont des pointeurs, ils sont toujours passés par référence aux fonctions. Pour rappel, cela signifie que la fonction reçoit l'adresse du tableau et non pas une copie du tableau. Ainsi, si la fonction modifie le tableau, ses modifications seront visibles en dehors de la fonction.
Une autre conséquence du fait qu'un tableau est un pointeur est qu'il n'est pas possible de connaître sa taille. Il faut donc passer la taille du tableau en paramètre de la fonction en plus du tableau lui-même.
La syntaxe int tab[] dans la déclaration de la fonction modify est équivalente à int* tab, c'est juste une bonne pratique pour indiquer que le paramètre est censé être un tableau.
Pas de return de tableau déclaré dans la stack
Étant donné que les tableaux déclarés dans la stack sont automatiquement libérés à la fin de l'exécution des fonctions, il n'est pas possible de retourner un tableau déclaré dans la stack depuis une fonction. En effet, si on le faisait, le tableau renvoyé pointerait vers une zone mémoire qui n'est plus valide, ce qui provoquerait un comportement indéfini lors de son utilisation.
Pour contourner ce problème, il y a deux solutions possibles :
- Allouer dynamiquement le tableau dans la heap avec
mallocet le renvoyer. Dans ce cas, il faudra penser à libérer la mémoire avecfreelorsque le tableau ne sera plus nécessaire.
- Ne pas déclarer le tableau dans la fonction, mais le déclarer dans la fonction appelante et le passer en paramètre, comme dans l'exemple du passage par référence.
Bonnes pratiques
Allocation dynamique que lorsque nécéssaire
L'allocation dynamique est plus lente que l'allocation statique et elle nécessite de gérer manuellement la libération de la mémoire. Il est donc préférable de n'utiliser l'allocation dynamique que lorsque c'est vraiment nécessaire, c'est-à-dire lorsque la taille du tableau n'est pas connue à la compilation ou lorsqu'on a besoin d'un tableau très grand qui ne tiendrait pas dans la stack.
INGInious