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à 5h35min58sd3à 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 (lemainaffiche 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 minutesvoid additionner_durees(const Duree *d1, const Duree *d2, Duree *resultat), qui calculeresultat = d1 + d2, puis normaliseresultat
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 ;
srandne 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)(lemainaffiche 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 :
Demander le nombre
nde chaînes, qui doit être> 0(sinon le programme se termine). Lirenavecread_line(donnée ci-dessous) puisatoi. (Avecscanf, le\nlaissé dans le buffer d’entrée serait lu par lefgetssuivant comme une ligne vide)Allouer dynamiquement un tableau
arrdenpointeurschar *(char **arr)Pour chaque case
i: lire la chaîne dans un buffer de taille fixe (char buf[256]) avecread_line, allouerstrlen(buf) + 1octets pourarr[i](+ 1 pour le\0), puis copierbufdansarr[i]avecstrcpyUne ligne trop longue pour le buffer est coupée :fgetslira la suite comme la chaîne suivante, ce qui est acceptable ici. En cas d’erreur à la casei(lecture oumalloc), libérer lesichaînes déjà allouées avecfree_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.Parcourir le tableau et afficher l’indice (à partir de 0) de la première occurrence de
"coucou"(comparer avecstrcmp, pas avec==)Afficher les chaînes du tableau
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înestrcpy(destination, source)pour copier une chaînefgets(buffer, taille, flux), qui, contrairement àscanf, lit aussi les espaces. Dans notre cas, le flux d’entrée (flux) serastdinatoi(chaine)pour convertir une chaîne en entierstrcmp(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_ligneset de colonnesnb_colonnes(strictement positifs : lire dans unint, vérifier> 0, puis convertir ensize_t)alloue dynamiquement une matrice (
int **, voir Tableau à deux dimensions, cas 1 : tableau de pointeurs) d’entiers de taillenb_lignes × nb_colonnesà l’aide d’une fonctionint **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’abordMatrice <nb_lignes>x<nb_colonnes> :, puis les éléments séparés par une tabulation (\t), et revient à la ligne à chaque nouvelle lignecalcule les sommes par ligne avec une fonction
int *sommes_lignes(int **matrice, size_t nb_lignes, size_t nb_colonnes), qui retourne un tableau denb_lignessommes (une par ligne) alloué dynamiquementcalcule les sommes par colonne avec une fonction
int *sommes_colonnes(int **matrice, size_t nb_lignes, size_t nb_colonnes), qui retourne un tableau denb_colonnessommes (une par colonne) alloué dynamiquementaffiche ces sommes dans le
main, séparées par une tabulation, sous les titres de la sortie attenduelibère la matrice avec une fonction
void liberer_matrice(int **matrice, size_t nb_lignes), sans oublier les tableaux retournés parsommes_lignesetsommes_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 retournerNULLles sommes doivent partir de 0 :
callocalloue et met à zéroles tableaux retournés par
sommes_lignesetsommes_colonnessont alloués dans ces fonctions, mais c’est lemainqui doit les libérer (voir Propriété de la mémoire)liberer_matricelibère chaque ligne, puis le tableau des lignes (voir l’ordre desfreedans 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