scanf est le miroir de printf : même en-tête, chaînes de format d'apparence similaire, direction opposée. Il lit du texte depuis l'entrée standard, le convertit selon le format, et stocke les résultats via les pointeurs que vous lui donnez.
C'est aussi la fonction qui produit plus de débutants perdus que toute autre en C, car trois de ses comportements sont surprenants et l'un d'eux est un trou de sécurité. Cette page couvre les quatre.
La forme de base, et pourquoi &
Tapez un nombre et appuyez sur Entrée pour l'exécuter. Le &age n'est pas décoratif. Le C passe chaque argument par valeur, donc si vous écriviez scanf("%d", age), la fonction recevrait une copie de la valeur actuelle (aberrante) de age et n'aurait aucun moyen d'atteindre la variable elle-même. &age passe l'adresse de la variable, dans laquelle scanf écrit ensuite. Voir les pointeurs pour le tableau complet.
Omettre le & est l'erreur scanf la plus courante. Le compilateur avec -Wall l'attrape (format '%d' expects argument of type 'int *') ; sans avertissements activés, cela compile et corrompt la mémoire à une adresse quelconque.
Lire plusieurs valeurs
Un seul appel peut lire plusieurs valeurs. Les espaces dans l'entrée - espaces, tabulations, sauts de ligne - les séparent, et les spécificateurs numériques en sautent n'importe quelle quantité.
Les espaces littéraux dans "%d %d %d" sont en fait redondants - %d saute déjà les espaces initiaux - mais ils rendent le format lisible. Ce qui n'est pas redondant, c'est de vérifier la valeur de retour, traitée ci-dessous.
Les caractères non blancs du format doivent correspondre exactement à l'entrée, c'est ainsi qu'on lit une saisie structurée :
Le : du format exige un : dans l'entrée. Tapez 9:30 et cela s'analyse ; tapez 9 30 et cela échoue après avoir lu le 9.
Vérifiez toujours la valeur de retour
scanf renvoie le nombre d'éléments affectés avec succès - pas le nombre de caractères, ni un indicateur de succès. Il renvoie EOF si l'entrée s'est terminée avant toute conversion.
Tapez bonjour à l'invite. scanf renvoie 0, n est laissé intact - contenant toujours le garbage de départ - et le texte fautif est toujours dans le flux d'entrée. Cette dernière partie est ce qui transforme une seule mauvaise saisie en boucle infinie :
/* BUG: tourne indefiniment sur une saisie non numerique */
while (scanf("%d", &n) != 1) {
printf("Reessayez : ");
}
scanf ne consomme jamais le texte qu'il n'a pas pu convertir, donc l'appel suivant échoue sur les mêmes caractères, indéfiniment. Pour vous en remettre, vous devez jeter le reste de la ligne vous-même :
Le test EOF est important : quand le flux d'entrée se termine (l'utilisateur appuie sur Ctrl+D, ou un fichier redirigé arrive à sa fin), scanf renvoie EOF pour toujours ensuite, si bien qu'une boucle qui teste seulement != 1 réessaie éternellement. Traitez « saisie invalide » et « plus de saisie » comme les deux cas différents qu'ils sont. Une fois cela en place, cette fonction auxiliaire discard_line mérite d'être gardée sous la main dans tout programme utilisant scanf sur une saisie humaine.
Le débordement de tampon de %s
%s lit des caractères dans un tableau de char. Notez l'absence de & - un nom de tableau se dégrade déjà en pointeur sur son premier élément, donc &name serait du mauvais type.
Le problème est que %s seul n'a aucune idée de la taille de votre tableau :
char name[10];
scanf("%s", name); /* DANGEREUX : 40 caracteres tapes, 41 octets ecrits */
Rien n'arrête l'écriture à la fin du tableau. Elle continue dans ce qui suit en mémoire - d'autres variables, l'adresse de retour - c'est le classique débordement de tampon, et historiquement la classe de bug la plus exploitée en C. La solution est une largeur entre le % et le s, un de moins que la taille du tableau pour laisser la place au '\0' terminal :
La largeur doit être écrite comme un littéral dans la chaîne de format, ce qui la rend pénible à garder synchronisée avec la taille du tableau - une raison de plus pour laquelle la plupart du C de production utilise fgets.
La seconde surprise de %s : il s'arrête au premier espace. Tapez Ada Lovelace et name contient Ada ; Lovelace attend dans le flux pour la lecture suivante. %s lit un mot, pas une ligne.
Le bug du saut de ligne résiduel
C'est le « scanf ne fonctionne pas » que tout le monde rencontre. Les spécificateurs numériques sautent les espaces initiaux ; %c non.
Voici ce qui se passe sans cet espace. Vous tapez 30 et appuyez sur Entrée. %d consomme le 3 et le 0 et s'arrête au saut de ligne, laissant '\n' dans le tampon. Le scanf("%c", &initial) suivant lit ce saut de ligne comme le caractère et retourne instantanément - l'invite apparaît et est sautée dans le même souffle.
Un espace dans une chaîne de format scanf signifie « sauter ici n'importe quelle quantité d'espaces », donc " %c" enjambe le saut de ligne égaré et attend un vrai caractère. L'espace n'est nécessaire qu'avant %c et %[ ; tous les autres spécificateurs sautent les espaces d'eux-mêmes.
Lire des flottants et des caractères
Les spécificateurs sont en grande partie partagés avec printf, avec une différence importante : scanf doit connaître la taille exacte de la destination, car il écrit à travers un pointeur.
Essayez 1.5 2.25 7 x.
%f lit un float, %lf lit un double. Dans printf, les deux s'affichent pareil parce que les float sont promus en double avant l'appel - mais dans scanf il n'y a pas de promotion, et utiliser %f avec un double * écrit quatre octets dans une variable de huit octets, la laissant corrompue. Le %c ici n'a pas besoin d'espace initial car le %d qui le précède s'est arrêté sur un espace, pas sur un saut de ligne. (Tableau complet sur spécificateurs de format.)
fgets : l'alternative robuste
Pour tout ce qu'une personne tape, lisez la ligne entière et analysez-la ensuite. fgets prend la taille du tampon en argument, il ne peut donc physiquement pas déborder, et quand la ligne tient il consomme aussi le saut de ligne, si bien que rien ne reste pour perturber la lecture suivante. (Une ligne plus longue que le tampon laisse sa fin non lue - l'absence de '\n' dans ce que vous avez reçu est la façon de le détecter.)
Trois points sur ce motif :
sizeof linepasse la vraie taille, donc changer la taille du tableau ne demande aucune autre modification - contrairement au littéral%19s.fgetsconserve le saut de ligne si la ligne tenait, c'est pourquoi le découpage parstrcspnest là. C'est aussi ainsi que l'on détecte une ligne trop longue pour le tampon : pas de'\n'dans ce que vous avez reçu.- Analysez ensuite.
strtolpour les entiers (il peut aussi vous dire où l'analyse s'est arrêtée),strtodpour le flottant, ousscanf(line, "%d %d", &a, &b)pour appliquer un format scanf à une chaîne que vous détenez déjà en sécurité.
sscanf mérite une mention à part : c'est scanf lisant depuis une chaîne au lieu du flux d'entrée, et il se combine parfaitement avec fgets. Vous obtenez l'analyse pratique de scanf sans aucun de ses problèmes de flux.
Erreurs courantes
- Oublier le
&sur un argument non tableau. Compilez avec-Wall. %ssans largeur. Un débordement de tampon qui attend une longue saisie.- Ignorer la valeur de retour. Puis lire une variable que
scanfn'a jamais affectée. - Boucler sur un
scanfen échec sans vider l'entrée. Une boucle infinie. %csans espace initial après avoir lu un nombre. Le bug de l'invite sautée.- Utiliser
%fpour undouble. Corruption silencieuse ;scanfdemande%lf. - Attendre de
%squ'il lise une ligne. Il lit un mot délimité par des espaces.
Questions fréquentes
Pourquoi scanf a-t-il besoin d'un & en C ?
Parce que le C passe les arguments par valeur. scanf doit modifier votre variable, il lui faut donc son adresse, pas une copie de sa valeur : scanf("%d", &age). La seule exception est un tableau de caractères, dont le nom est déjà une adresse - scanf("%19s", name) ne prend pas de &.
Pourquoi scanf saute-t-il ma saisie pour un char ou une chaîne ?
Un scanf("%d", ...) précédent a laissé le saut de ligne de la touche Entrée dans le tampon d'entrée. %d et %f sautent les espaces initiaux, mais %c non, il lit donc ce saut de ligne au lieu d'attendre. La solution est un espace dans le format - scanf(" %c", &ch) - qui dit à scanf de sauter d'abord les espaces.
Comment lire une chaîne avec scanf en toute sécurité en C ?
Donnez à %s une largeur maximale d'un de moins que votre tampon : char name[20]; scanf("%19s", name);. Sans la largeur, %s écrit autant de caractères que l'utilisateur en tape et dépasse allègrement la fin du tableau. Rappelez-vous aussi que %s s'arrête au premier espace, il ne peut donc pas lire un nom complet contenant une espace.
Faut-il utiliser scanf ou fgets en C ?
Utilisez fgets pour tout ce qu'une personne tape. Il lit une ligne entière dans un tampon dimensionné, ne peut pas déborder, et (quand la ligne tient) consomme aussi le saut de ligne, donc les classiques bugs de saisie mixte disparaissent. Analysez la ligne ensuite avec sscanf ou strtol. scanf convient pour de petits exercices et des entrées machine bien formées.