Partie 1 - Bases de C

Pour l’organisation des fichiers (dossier p1/) et les options de compilation, voir les consignes. Pour l’utilisation de valgrind (et de l’option -g), voir la page Outils.

p1e1 : pointeurs simples, pointeurs et tableaux

Avant de commencer, relire le récapitulatif du passage par adresse (quand écrire * et &).

Écrire une fonction division_et_reste qui effectue la division entière de deux entiers a et b :

int division_et_reste(int a, int b, int *quotient, int *reste);

La fonction stocke le quotient et le reste dans deux variables passées par adresse. Sa valeur de retour est réservée à un code d’erreur : 0 si tout s’est bien passé, -1 si les paramètres sont invalides (a < 0 ou b <= 0) ; dans ce cas, *quotient et *reste ne sont pas modifiés.

Écrire une fonction min_et_max qui recherche, dans un tableau d’entiers de taille taille, le plus petit et le plus grand élément :

int min_et_max(const int *tab, size_t taille, int *valeur_min, int *valeur_max);

Les résultats seront retournés par deux variables passées par adresse. Comme pour division_et_reste, la fonction retourne 0 en cas de succès et -1 si le tableau est vide (taille == 0).

Dans le main, vérifier la valeur de retour de chaque appel. En cas d’erreur, afficher un message sur la sortie d’erreur avec fprintf(stderr, ...) (perror n’a pas de sens ici, car errno n’est pas positionné, voir Gestion des erreurs) et ne pas utiliser les résultats.

Pour aller plus loin, adapter ensuite les deux fonctions pour qu’elles positionnent errno et utiliser perror dans le main (voir positionner errno soi-même).

Tester avec a = 17, b = 5, puis a = 17, b = 0, et avec le tableau {5, 9, 2, 7, 3} déclaré dans le main, puis avec une taille de 0.

Résultat attendu (afficher le tableau avec ses éléments séparés par des virgules, puis le minimum et le maximum, chacun précédé d’une tabulation, \t) :

17 / 5 = 3, reste = 2
Erreur : division de 17 par 0 impossible

Tableau : 5, 9, 2, 7, 3
        - min : 2
        - max : 9
Erreur : min et max d'un tableau vide

p1e2 : pointeurs et structures

Définir un type Duree (typedef struct { ... } Duree;) contenant 3 entiers non signés (unsigned int) : heures, minutes, secondes.

Dans le main, déclarer et initialiser :

  • d1 à 0h150min500s (valeur volontairement non normalisée, qui donne 2h38min20s une fois normalisée)

  • d2 à 5h35min58s

  • d3 à 2h45min06s

Implémenter :

  • void afficher_duree(const Duree *d), qui affiche la durée au format hh:mm:ss (ex. 05:35:58, 02:45:06), suivie d’un retour à la ligne (le main affiche le texte avant). Utiliser %02u : au moins 2 chiffres, complétés par des 0 (une valeur plus grande, comme 150, est affichée en entier)

  • void normaliser_duree(Duree *d), qui normalise la durée (ex. 75s → 1min15s, 70min130s → 1h12min10s). Les heures ne sont pas limitées à 24. Indice : normaliser d’abord les secondes (la retenue s’ajoute aux minutes), puis les minutes

  • void additionner_durees(const Duree *d1, const Duree *d2, Duree *resultat), qui calcule resultat = d1 + d2, puis normalise resultat

Les structures sont ici passées par adresse pour s’entraîner à -> et à const (voir le schéma d’une structure en mémoire).

Tester ces fonctions avec d1, d2, d3 et une variable Duree somme; (une structure, pas un pointeur : on passe &somme aux fonctions) pour stocker les résultats : afficher d1 brut, le normaliser puis l’afficher, afficher d2 et d3, puis les trois sommes.

Résultat attendu :

d1 brut      : 00:150:500
d1 normalisé : 02:38:20
d2           : 05:35:58
d3           : 02:45:06
d1 + d2 = 08:14:18
d2 + d3 = 08:21:04
d1 + d3 = 05:23:26

p1e3 : création de tableau dynamique

Écrire un programme C qui :

  • demande une taille de tableau (qui doit être > 0, sinon le programme se termine)

  • alloue dynamiquement un tableau d’entiers de cette taille (voir Allocation dynamique)

  • remplit le tableau avec des valeurs aléatoires (voir Aléatoire ; srand ne doit être appelé qu’une seule fois) comprises dans l’intervalle [50, 75[

  • affiche le contenu du tableau avec une fonction void afficher_tableau(const int *tab, size_t taille) (le main affiche le texte avant ; la fonction affiche les valeurs séparées par des espaces, puis \n)

Vérifier qu’il n’y a pas de fuites mémoire (voir Analyse à l’exécution).

Résultat attendu :

$ gcc -std=c2x -Wall -Wextra -pedantic -g p1/p1e3.c && valgrind --leak-check=full ./a.out
... affichage de valgrind
Taille du tableau (>0) : 20
Contenu du tableau : 58 65 62 57 63 61 58 66 54 74 61 66 61 57 74 59 54 74 64 71
... affichage de valgrind sans fuites mémoire

p1e4 : modification de tableau dynamique

Reprendre le code précédent. Demander une nouvelle taille à l’utilisateur, puis redimensionner le tableau avec realloc en conservant les valeurs existantes (utiliser un pointeur temporaire, voir le schéma de realloc). Si la nouvelle taille est plus grande, remplir les nouvelles cases avec des valeurs aléatoires : elles ne sont pas initialisées par realloc. Garder l’ancienne taille dans une variable pour savoir à partir de quel indice remplir. Si l’une des deux tailles saisies est <= 0, le programme se termine (en libérant la mémoire déjà allouée).

Afficher ensuite le nouveau tableau.

Vérifier qu’il n’y a pas de fuites mémoire (voir Analyse à l’exécution).

Résultat attendu :

$ gcc -std=c2x -Wall -Wextra -pedantic -g p1/p1e4.c && valgrind --leak-check=full ./a.out
... affichage de valgrind
Taille du tableau (>0) : 20
Tableau initial : 53 62 60 60 74 70 61 61 65 68 57 63 72 61 64 65 50 53 58 64
Nouvelle taille (>0) : 10
Nouveau tableau : 53 62 60 60 74 70 61 61 65 68
... affichage de valgrind sans fuites mémoire
$ gcc -std=c2x -Wall -Wextra -pedantic -g p1/p1e4.c && valgrind --leak-check=full ./a.out
... affichage de valgrind
Taille du tableau (>0) : 10
Tableau initial : 50 61 60 50 58 65 52 73 52 66
Nouvelle taille (>0) : 20
Nouveau tableau : 50 61 60 50 58 65 52 73 52 66 70 70 71 50 63 58 68 53 74 54
... affichage de valgrind sans fuites mémoire

p1e5 : tableau de chaînes de caractères

Le programme lit n chaînes au clavier, les range dans un tableau alloué dynamiquement, cherche la chaîne "coucou" puis affiche toutes les chaînes. En mémoire, on veut obtenir la même organisation que dans l’exemple Tableau de chaînes du cours : un tableau de n pointeurs, chacun vers sa propre copie de la chaîne saisie.

Étapes :

  1. Demander le nombre n de chaînes, qui doit être > 0 (sinon le programme se termine). Lire n avec read_line (donnée ci-dessous) puis atoi. (Avec scanf, le \n laissé dans le buffer d’entrée serait lu par le fgets suivant comme une ligne vide)

  2. Allouer dynamiquement un tableau arr de n pointeurs char * (char **arr)

  3. Pour chaque case i : lire la chaîne dans un buffer de taille fixe (char buf[256]) avec read_line, allouer strlen(buf) + 1 octets pour arr[i] (+ 1 pour le \0), puis copier buf dans arr[i] avec strcpy Une ligne trop longue pour le buffer est coupée : fgets lira la suite comme la chaîne suivante, ce qui est acceptable ici. En cas d’erreur à la case i (lecture ou malloc), libérer les i chaînes déjà allouées avec free_strings(arr, i) (étape 6)

    Question : essayez arr[i] = buf; à la place de l’allocation et de la copie : qu’affiche l’étape 5 ? Expliquez avec un schéma de la mémoire comme celui du cours.

  4. Parcourir le tableau et afficher l’indice (à partir de 0) de la première occurrence de "coucou" (comparer avec strcmp, pas avec ==)

  5. Afficher les chaînes du tableau

  6. Libérer la mémoire avec une fonction void free_strings(char **arr, size_t n) : chaque chaîne, puis le tableau de pointeurs

Fonctions utiles (inclure <string.h> et <stdlib.h> ; bool est un mot-clé en C23, <stdbool.h> n’est pas nécessaire) :

  • strlen(chaine) pour obtenir la longueur d’une chaîne

  • strcpy(destination, source) pour copier une chaîne

  • fgets(buffer, taille, flux), qui, contrairement à scanf, lit aussi les espaces. Dans notre cas, le flux d’entrée (flux) sera stdin

  • atoi(chaine) pour convertir une chaîne en entier

  • strcmp(s1, s2) pour comparer deux chaînes (retourne 0 si elles sont égales)

Voici une fonction read_line qui lit une entrée utilisateur et retire le \n :

/**
 * @brief Lit une ligne (y compris les espaces) dans buffer et retire le '\n'
 *
 * @param buffer tableau dans lequel écrire les données
 * @param taille nombre maximal de caractères (taille du tableau)
 * @return true si la lecture a réussi, false sinon (fin de l'entrée ou erreur)
 */
bool read_line(char *buffer, int taille) {
    if (fgets(buffer, taille, stdin) == NULL) {
        buffer[0] = '\0';
        return false;
    }
    buffer[strcspn(buffer, "\n")] = '\0';
    return true;
}

Vérifier qu’il n’y a pas de fuites mémoire (voir Analyse à l’exécution).

Résultat attendu :

$ gcc -std=c2x -Wall -Wextra -pedantic -g p1/p1e5.c && valgrind --leak-check=full ./a.out
... affichage de valgrind
Nombre de chaînes (>0) : 3
Chaîne 1 : hello
Chaîne 2 : hey
Chaîne 3 : hej

À la recherche de coucou...
coucou n'est pas dans le tableau

Affichage final :
[0] hello
[1] hey
[2] hej
... affichage de valgrind sans fuites mémoire
$ valgrind --leak-check=full ./a.out
... affichage de valgrind
Nombre de chaînes (>0) : 3
Chaîne 1 : hello
Chaîne 2 : coucou
Chaîne 3 : hej

À la recherche de coucou...
coucou est trouvé à l'indice 1

Affichage final :
[0] hello
[1] coucou
[2] hej
... affichage de valgrind sans fuites mémoire

p1e6 : création de matrice dynamique

Écrire un programme C qui :

  • demande à l’utilisateur un nombre de lignes nb_lignes et de colonnes nb_colonnes (strictement positifs : lire dans un int, vérifier > 0, puis convertir en size_t)

  • alloue dynamiquement une matrice (int **, voir Tableau à deux dimensions, cas 1 : tableau de pointeurs) d’entiers de taille nb_lignes × nb_colonnes à l’aide d’une fonction int **allouer_matrice(size_t nb_lignes, size_t nb_colonnes)

  • la remplit avec des valeurs aléatoires dans l’intervalle [20, 60[

  • l’affiche avec une fonction void afficher_matrice(int **matrice, size_t nb_lignes, size_t nb_colonnes), qui affiche d’abord Matrice <nb_lignes>x<nb_colonnes> :, puis les éléments séparés par une tabulation (\t), et revient à la ligne à chaque nouvelle ligne

  • calcule les sommes par ligne avec une fonction int *sommes_lignes(int **matrice, size_t nb_lignes, size_t nb_colonnes), qui retourne un tableau de nb_lignes sommes (une par ligne) alloué dynamiquement

  • calcule les sommes par colonne avec une fonction int *sommes_colonnes(int **matrice, size_t nb_lignes, size_t nb_colonnes), qui retourne un tableau de nb_colonnes sommes (une par colonne) alloué dynamiquement

  • affiche ces sommes dans le main, séparées par une tabulation, sous les titres de la sortie attendue

  • libère la matrice avec une fonction void liberer_matrice(int **matrice, size_t nb_lignes), sans oublier les tableaux retournés par sommes_lignes et sommes_colonnes

Prototypes à placer avant le main :

int **allouer_matrice(size_t nb_lignes, size_t nb_colonnes);
void afficher_matrice(int **matrice, size_t nb_lignes, size_t nb_colonnes);
int *sommes_lignes(int **matrice, size_t nb_lignes, size_t nb_colonnes);
int *sommes_colonnes(int **matrice, size_t nb_lignes, size_t nb_colonnes);
void liberer_matrice(int **matrice, size_t nb_lignes);

Indices :

  • dans allouer_matrice, si l’allocation d’une ligne échoue, libérer ce qui a déjà été alloué (comme dans l’exemple du cours), puis retourner NULL

  • les sommes doivent partir de 0 : calloc alloue et met à zéro

  • les tableaux retournés par sommes_lignes et sommes_colonnes sont alloués dans ces fonctions, mais c’est le main qui doit les libérer (voir Propriété de la mémoire)

  • liberer_matrice libère chaque ligne, puis le tableau des lignes (voir l’ordre des free dans Tableau à deux dimensions)

Vérifier qu’il n’y a pas de fuites mémoire (voir Analyse à l’exécution).

Résultat attendu :

$ gcc -std=c2x -Wall -Wextra -pedantic -g p1/p1e6.c && valgrind --leak-check=full ./a.out
... affichage de valgrind
Nombre de lignes (>0) : 5
Nombre de colonnes (>0) : 3
Matrice 5x3 :
41      54      24
51      57      23
41      32      36
28      39      47
23      33      30
Somme des lignes :
119     131     109     114     86
Somme des colonnes :
184     215     160
... affichage de valgrind sans fuites mémoire