On commence par un des premiers exercices de ma feuille de TD du cours algorithmique et programmation, en première année de licence (Bac+1). À ce stade, les étudiant⋅es ont déjà vu un programme qui lit le prénom et répond “Bonjour PRÉNOM.” Je ne vais pas aider le vampire, je lui donne juste l’énoncé de mon exercice et il va s’en débrouiller. Entretien réalisé le 11/02/2025.
Écrivez un programme qui lit au clavier un prénom, un nom, et un âge puis affiche le message “Bonjour PRÉNOM NOM, vous avez ÂGE ans.” où les mots en majuscules sont remplacés par les valeurs lues au clavier.
Voici un exemple de programme en Python qui lit un prénom, un nom et un âge au clavier, puis affiche le message demandé :
Je coupe la suite. Par défaut, le vampire me répond toujours en Python, il m’arrivera de nouveau de me faire piéger, car évidemment mes TD ne précisent pas à chaque exercice qu’on travaille en C.
En C plutôt qu’en Python.
Voici un exemple de programme en C qui lit un prénom, un nom et un âge au clavier, puis affiche le message demandé :
#include <stdio.h>
int main() {
// Déclaration des variables pour stocker le prénom, le nom et l'âge
char prenom[50];
char nom[50];
int age;
// Demander à l'utilisateur de saisir son prénom
printf("Entrez votre prénom : ");
scanf("%s", prenom);
// Demander à l'utilisateur de saisir son nom
printf("Entrez votre nom : ");
scanf("%s", nom);
// Demander à l'utilisateur de saisir son âge
printf("Entrez votre âge : ");
scanf("%d", &age);
// Afficher le message avec les valeurs saisies
printf("Bonjour %s %s, vous avez %d ans.\n", prenom, nom, age);
return 0;
}
Vous pouvez compiler et exécuter ce programme dans un environnement C. Il demandera à l’utilisateur de saisir son prénom, son nom et son âge, puis affichera le message formaté avec les valeurs saisies.
C’est bien présenté, mais il y a un gros problème dans ce programme. Je vais poser le doigt dessus et le vampire va immédiatement réagir en identifiant le problème et en proposant une solution. Évidemment, moi je sais qu’il a un problème et je peux le pointer. Mes étudiant⋅es vont probablement se contenter cette réponse. Sur ce point soyons honnête : c’est la réponse qu’on trouvera sur Internet en cherchant “lire une chaîne de caractère au clavier en C”, et il faudra faire défiler un peu la page pour trouver un pauvre commentaire bien isolé qui essaie d’avertir que cette solution est mauvaise.
Que se passe-t-il si un utilisateur de ce programme tape un prénom de plus de 50 caractères ?
Si un utilisateur tape un prénom ou un nom de plus de 50 caractères, le programme en C tel qu’il est écrit ci-dessus peut rencontrer des problèmes de dépassement de tampon (buffer overflow). Cela peut entraîner un comportement imprévisible, y compris des plantages du programme ou des vulnérabilités de sécurité.
Pour éviter ce problème, vous pouvez limiter la taille des entrées en utilisant une fonction comme fgets
au lieu de scanf
, qui permet de spécifier la taille maximale de l’entrée. Voici comment vous pouvez modifier le programme pour gérer ce cas :
#include <stdio.h>
int main() {
// Déclaration des variables pour stocker le prénom, le nom et l'âge
char prenom[50];
char nom[50];
int age;
// Demander à l'utilisateur de saisir son prénom
printf("Entrez votre prénom : ");
fgets(prenom, sizeof(prenom), stdin);
// Supprimer le caractère de nouvelle ligne si présent
prenom[strcspn(prenom, "\n")] = 0;
// Demander à l'utilisateur de saisir son nom
printf("Entrez votre nom : ");
fgets(nom, sizeof(nom), stdin);
// Supprimer le caractère de nouvelle ligne si présent
nom[strcspn(nom, "\n")] = 0;
// Demander à l'utilisateur de saisir son âge
printf("Entrez votre âge : ");
scanf("%d", &age);
// Afficher le message avec les valeurs saisies
printf("Bonjour %s %s, vous avez %d ans.\n", prenom, nom, age);
return 0;
}
Dans ce code, fgets
est utilisé pour lire les chaînes de caractères, ce qui permet de limiter la taille de l’entrée à 50 caractères (y compris le caractère de fin de chaîne \0
). La fonction strcspn
est utilisée pour supprimer le caractère de nouvelle ligne (\n
) qui peut être inclus dans l’entrée lorsque l’utilisateur appuie sur Entrée.
La réponse est correcte, l’explication donnée est bonne et précise, la solution proposée fonctionne. Mais cette solution ne me convient pas parce qu’elle implique de comprendre beaucoup trop de choses sur le fonctionnement du langage à ce stade de mon cours. Donc je vais essayer d’amener le vampire à la solution que je présente en cours (sans prétendre que c’est la meilleure, de toute façon il n’y a pas de bonne solution quand on débute).
Peut-on trouver une solution sans débordement de tampon et en utilisant scanf à condition d’utiliser le compilateur GCC ?
Je fais exprès de faire référence à GCC pour donner un indice, mais le vampire ne saisit pas l’allusion, en tout cas pas comme je l’aurais souhaité (ce que je ne lui reproche pas). On devra y revenir de façon plus explicite.
Oui, il est possible d’utiliser scanf
de manière sécurisée pour éviter les débordements de tampon en spécifiant une largeur maximale pour les chaînes de caractères. Cependant, il est important de noter que cette méthode fonctionne avec les compilateurs conformes au standard C99 et ultérieurs, y compris GCC.
Voici comment vous pouvez modifier le programme pour utiliser scanf
de manière sécurisée :
#include <stdio.h>
int main() {
// Déclaration des variables pour stocker le prénom, le nom et l'âge
char prenom[50];
char nom[50];
int age;
// Demander à l'utilisateur de saisir son prénom
printf("Entrez votre prénom : ");
scanf("%49s", prenom); // Limiter la taille de l'entrée à 49 caractères + 1 pour le caractère de fin de chaîne
// Demander à l'utilisateur de saisir son nom
printf("Entrez votre nom : ");
scanf("%49s", nom); // Limiter la taille de l'entrée à 49 caractères + 1 pour le caractère de fin de chaîne
// Demander à l'utilisateur de saisir son âge
printf("Entrez votre âge : ");
scanf("%d", &age);
// Afficher le message avec les valeurs saisies
printf("Bonjour %s %s, vous avez %d ans.\n", prenom, nom, age);
return 0;
}
Dans ce code, scanf("%49s", prenom)
et scanf("%49s", nom)
limitent la taille des entrées à 49 caractères, plus un caractère pour le caractère de fin de chaîne (\0
). Cela empêche les débordements de tampon en garantissant que les chaînes de caractères ne dépassent pas la taille des tableaux prenom
et nom
.
Cette méthode est sûre et fonctionne avec le compilateur GCC, ainsi qu’avec d’autres compilateurs conformes au standard C99 et ultérieurs.
Bonne solution aussi, explications bonnes aussi. Je pousse vers ma solution.
Et avec “%ms” ?
L’utilisation de "%ms"
avec scanf
est une extension spécifique à GCC qui permet d’allouer dynamiquement de la mémoire pour les chaînes de caractères lues. Cela peut être utile pour éviter les débordements de tampon, car scanf
allouera automatiquement la mémoire nécessaire pour stocker la chaîne de caractères lue.
Voici comment vous pouvez utiliser "%ms"
pour lire des chaînes de caractères de manière sécurisée :
#include <stdio.h>
#include <stdlib.h>
int main() {
// Déclaration des variables pour stocker le prénom, le nom et l'âge
char *prenom = NULL;
char *nom = NULL;
int age;
// Demander à l'utilisateur de saisir son prénom
printf("Entrez votre prénom : ");
scanf("%ms", &prenom); // Utiliser %ms pour allouer dynamiquement de la mémoire
// Demander à l'utilisateur de saisir son nom
printf("Entrez votre nom : ");
scanf("%ms", &nom); // Utiliser %ms pour allouer dynamiquement de la mémoire
// Demander à l'utilisateur de saisir son âge
printf("Entrez votre âge : ");
scanf("%d", &age);
// Afficher le message avec les valeurs saisies
printf("Bonjour %s %s, vous avez %d ans.\n", prenom, nom, age);
// Libérer la mémoire allouée dynamiquement
free(prenom);
free(nom);
return 0;
}
Dans ce code, scanf("%ms", &prenom)
et scanf("%ms", &nom)
utilisent l’extension "%ms"
pour allouer dynamiquement de la mémoire pour les chaînes de caractères prenom
et nom
. Cela garantit que les chaînes de caractères lues ne dépasseront pas la mémoire allouée, évitant ainsi les débordements de tampon.
Il est important de libérer la mémoire allouée dynamiquement avec free(prenom)
et free(nom)
à la fin du programme pour éviter les fuites de mémoire.
Cette méthode est spécifique à GCC et peut ne pas être portable vers d’autres compilateurs qui ne supportent pas cette extension.
Tout cela est juste, et bien expliqué, y compris les conséquences de l’allocation dynamique de mémoire. Je vais pointer un autre problème dans le programme, on peut avoir des âges négatifs.
Est-ce qu’un âge peut être négatif ?
En général, un âge ne peut pas être négatif dans le contexte des êtres humains, car l’âge représente le nombre d’années écoulées depuis la naissance d’une personne. Par conséquent, un âge négatif n’aurait pas de sens logique.
Cependant, dans un programme informatique, il est possible qu’un utilisateur entre un âge négatif par erreur ou par malveillance. Pour gérer ce cas, vous pouvez ajouter une vérification dans votre programme pour vous assurer que l’âge saisi est valide (c’est-à-dire non négatif).
Voici comment vous pouvez modifier le programme pour inclure cette vérification :
#include <stdio.h>
#include <stdlib.h>
int main() {
// Déclaration des variables pour stocker le prénom, le nom et l'âge
char *prenom = NULL;
char *nom = NULL;
int age;
// Demander à l'utilisateur de saisir son prénom
printf("Entrez votre prénom : ");
scanf("%ms", &prenom); // Utiliser %ms pour allouer dynamiquement de la mémoire
// Demander à l'utilisateur de saisir son nom
printf("Entrez votre nom : ");
scanf("%ms", &nom); // Utiliser %ms pour allouer dynamiquement de la mémoire
// Demander à l'utilisateur de saisir son âge
printf("Entrez votre âge : ");
scanf("%d", &age);
// Vérifier si l'âge est valide
if (age < 0) {
printf("L'âge ne peut pas être négatif.\n");
// Libérer la mémoire allouée dynamiquement
free(prenom);
free(nom);
return 1; // Quitter le programme avec un code d'erreur
}
// Afficher le message avec les valeurs saisies
printf("Bonjour %s %s, vous avez %d ans.\n", prenom, nom, age);
// Libérer la mémoire allouée dynamiquement
free(prenom);
free(nom);
return 0;
}
Dans ce code, une vérification est ajoutée après la saisie de l’âge pour s’assurer qu’il n’est pas négatif. Si l’âge est négatif, le programme affiche un message d’erreur, libère la mémoire allouée dynamiquement et quitte avec un code d’erreur.
Sa réaction est plutôt bonne, mais sa solution n’est pas très maligne parce qu’il garde une déclaration de la variable age
qui permet les négatifs et alourdit son code pour l’interdire en pratique.
Ce qui amène aussi un code peu élégant sur la fin.
Je l’aide un peu.
Plutôt que tester si l’âge est négatif, est-ce qu’on peut le déclarer et le lire autrement ?
Oui, vous pouvez déclarer l’âge comme un type unsigned int
(entier non signé) pour garantir qu’il ne peut pas être négatif. En C, un unsigned int
ne peut contenir que des valeurs positives ou zéro. Cela permet de s’assurer que l’âge saisi ne sera jamais négatif.
Voici comment vous pouvez modifier le programme pour utiliser unsigned int
pour l’âge :
#include <stdio.h>
#include <stdlib.h>
int main() {
// Déclaration des variables pour stocker le prénom, le nom et l'âge
char *prenom = NULL;
char *nom = NULL;
unsigned int age; // Utiliser unsigned int pour garantir que l'âge ne peut pas être négatif
// Demander à l'utilisateur de saisir son prénom
printf("Entrez votre prénom : ");
scanf("%ms", &prenom); // Utiliser %ms pour allouer dynamiquement de la mémoire
// Demander à l'utilisateur de saisir son nom
printf("Entrez votre nom : ");
scanf("%ms", &nom); // Utiliser %ms pour allouer dynamiquement de la mémoire
// Demander à l'utilisateur de saisir son âge
printf("Entrez votre âge : ");
scanf("%u", &age); // Utiliser %u pour lire un unsigned int
// Afficher le message avec les valeurs saisies
printf("Bonjour %s %s, vous avez %u ans.\n", prenom, nom, age);
// Libérer la mémoire allouée dynamiquement
free(prenom);
free(nom);
return 0;
}
Dans ce code, unsigned int age
garantit que l’âge ne peut pas être négatif. Lors de la lecture de l’âge avec scanf
, le format %u
est utilisé pour lire un entier non signé. Cela empêche l’utilisateur de saisir un âge négatif, car scanf
interprétera toute entrée négative comme une valeur positive ou zéro.
En gros, nous voilà au genre de solution que presque toustes mes étudiant⋅es savent produire en se contentant de broder sur les exemples déjà vus en cours. Là il a fallu l’aider. Les différentes questions que j’ai posées, j’aurais pu les poser lors d’un entretien réel, et à peut près de cette façon. Donc le vampire s’adapte et produit un résultat qui va dans le sens que j’indique. Mais en première approche, il a produit quelque chose de moins bon que 80% de mes étudiant⋅es ayant suivi 3h de cours. Pourtant il en connait beaucoup plus, ce dont il fait la preuve. On voit bien là qu’il n’est pas humain, il garde ses bonnes réponses. Un⋅e étudiant⋅e qui en saurait autant que le vampire me donnerait tout de suite sa meilleure réponse. On est bien d’accord, le vampire ne sait rien, et c’est bien le problème.
Reste que l’expérience pour moi est troublante, je me promène en pleine vallée de l’étrange. Je discute avec une créature qui n’a rien d’humain mais ne le révèle que de façon détournée, ne sait rien mais est très convaincante pour faire comme si elle savait beaucoup, semble pouvoir aider mais fournit en première approche des réponses pas très bonnes (mais pas pires que l’internaute moyen).