Information

Deadline Geen deadline
Submission limit No limitation

Tags

Sign in

[7.1] Rappel des concepts [Obligatoire]

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.

ATTENTION!
En C, les indices des tableaux commencent à 0 et non pas à 1. C'est un peu contre-intuitif au début mais c'est plus logique en réalité car l'indice d'un tableau représente un décalage par rapport à son adresse de début et donc le premier élément est à l'adresse de début du tableau + 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 double de taille 10 non initialisé :

    double tableau[10];
    
  • Déclaration d'un tableau de char de taille 5 initialisé avec la même valeur 'b' :

    char tableau[5] = {'b'};  // équivalent à {'b', 'b', 'b', 'b', 'b'}
    
  • Déclaration simplifiée d'un tableau de int initialisé avec les valeurs 10, 20, 30 et 40 :

    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 float de taille n pour 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 fonction malloc n'alloue pas un tableau, elle alloue un bloc de mémoire d'une certaine taille mais sans type particulier et elle renvoie un pointeur void* vers ce bloc. Il faut donc convertir le pointeur renvoyé par malloc en pointeur vers le type souhaité (float* dans ce cas) pour interpréter le bloc de mémoire correctement.
  • sizeof(float) * (unsigned int)entree_utilisateur calcule la taille du bloc de mémoire à allouer. sizeof(float) renvoie la taille en octets d'un seul float et cette taille est ensuite multipliée par le nombre d'éléments souhaités dans le tableau (entree_utilisateur dans ce cas, mais qui est casté en unsigned int car malloc requiert un int non-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 :

  1. Allouer dynamiquement le tableau dans la heap avec malloc et le renvoyer. Dans ce cas, il faudra penser à libérer la mémoire avec free lorsque le tableau ne sera plus nécessaire.
  1. 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.


Question 1: Tableaux

Déclarez un tableau d'entiers avec comme identifiant tab et de taille 5. N'oubliez pas le ; à la fin.

Question 2: Initialiser tableau (partie 1)

Étant donné le tableau de l'exercice précédent, initialisez son premier élément à la valeur 25.

Question 3: Initialiser tableau (partie 2)

Étant donné le tableau de l'exercice précédent, initialisez le dernier élément du tableau avec la valeur 28.

Question 4: Initialiser tableau (partie 3)

Étant donné le tableau de l'exercice précédent, initialisez, en une seule instruction, le 2e élément du tableau avec une valeur entrée par l'utilisateur.

Question 5: Initialiser tableau (partie 4)

Étant donné le tableau de l'exercice précédent, initialisez le 3e élément du tableau comme la somme du premier et du dernier élément.

Question 6: Indice tableau
int ages[3];

int i = 0;

ages[i] = 4;

Dans le programme ci-dessus, quel élément du tableau vaut maintenant 4 ?

Question 7: tableau[i]
 1 // PRE: tresors est de taille nombre et nombre > 0
 2 // POST: renvoie l'indice du plus grand trésor
 3 int plus_grand_tresor(int tresors[], int nombre) {
 4     int plus_grand = 0;
 5     for (int i = 0; i < nombre; i++) {
 6         if (tresors[plus_grand] < tresors[i]) {
 7             plus_grand = i;
 8         }
 9     }
10     return plus_grand;
11 }
12 
13 int main(void) {
14     int tresors[5] = {0};
15     tresors[0] = 4;
16     tresors[3] = 10;
17     tresors[2] = 1;
18     return 0;
19 }

Dans le programme ci-dessus, tresors[i] est ...

Question 8: tresor[i] type

Étant donné le programme de l'exercice précédent, tresor[i] est de quel type ? Donnez le nom du type C !

Question 9: Taille

À la ligne 14 du programme de l'exercice précédent, on déclare un tableau trésors et on initialise tous ses éléments à zéro. Quelle est la taille de ce tableau ?

Question 10: Malloc et Free

Cochez les affirmations vraies concernant les fonctions malloc et free :

Question 11: Utilisation d'un tableau (Après la ligne 2)
 1 int main(void) {
 2     int t[3] = {0};
 3     for (int i = 0; i < 3; i++) {
 4         t[i] = i;
 5     }
 6     t[0] = t[1] * t[2];
 7 
 8     int tableau[8] = {0};
 9     for (int i = 0; i < 8; i++) {
10         if (i % 3 == 0) {
11             tableau[(i + 1) % 8] = 1;
12         }
13     }
14 
15     return 0;
16 }

Dans le programme ci-dessus, quel est le contenu du tableau t après l'exécution de la ligne 2 ?

ATTENTION!

La convention d'écriture pour cet exercice est la suivante :

  • Si un tableau a une taille de 3 et ne contient que des 1, écrivez-le comme cela : {1, 1, 1}.
  • S'il contenait plutôt un 1, un 2 et un 4, il s'écrirait : {1, 2, 4}.
Question 12: Utilisation d'un tableau (Après la première boucle)

Dans le programme de l'exercice précédent, quel est le contenu du tableau t après l'exécution de la première boucle (c'est-à-dire après la ligne 5) ?

Question 13: Utilisation d'un tableau (Après la ligne 6)

Dans le programme de l'exercice précédent, quel est le contenu du tableau t après l'exécution de la ligne 6 ?

Question 14: Utilisation d'un tableau (Après la deuxième boucle)

Dans le programme de l'exercice précédent, quel est le contenu du tableau tableau après l'exécution de la deuxième boucle (c'est-à-dire après la ligne 13) ?