Menu

printf en C : chaînes de format, spécificateurs et affichage des flottants

Comment printf fonctionne réellement en C - le modèle de la chaîne de format, les spécificateurs du quotidien, largeur et précision pour une sortie alignée, l'affichage sensé des flottants, la valeur de retour, et pourquoi printf(saisie_utilisateur) est un trou de sécurité.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

printf est la première fonction que tout programmeur C apprend et celle dont la plupart ne lisent jamais entièrement le manuel. Elle vaut dix minutes, car sa chaîne de format est un petit langage à part entière - et parce que se tromper de spécificateur n'est pas une faute de frappe, c'est un comportement indéfini.

Elle vit dans <stdio.h>, donc tout programme qui l'utilise commence par #include <stdio.h>.

Le modèle de la chaîne de format

printf parcourt la chaîne de format un caractère à la fois. Tout ce qui n'est pas un % est affiché exactement tel qu'écrit. Un % commence une spécification de conversion, qui consomme l'argument suivant et l'affiche sous la forme demandée.

Les arguments sont appariés aux spécificateurs strictement par position : le premier % prend count, le second prend name, et ainsi de suite. printf n'a aucun moyen de vérifier que vous avez passé ce que vous promettiez - il ne peut pas voir les types - donc la chaîne de format est un contrat que vous devez tenir. C'est ce qui rend -Wall digne d'être activé : GCC lit la chaîne de format littérale à la compilation et avertit quand un argument ne correspond pas.

Rien ne force un saut de ligne. printf("Bonjour") laisse le curseur après le r, c'est pourquoi presque toute chaîne de format se termine par \n.

Les spécificateurs du quotidien

Cinq couvrent la plupart du code :

SpécificateurAfficheExemple d'appelSortie
%dun int signéprintf("%d", -42)-42
%fun doubleprintf("%f", 2.5)2.500000
%sune chaîne (char *)printf("%s", "hi")hi
%cun caractère uniqueprintf("%c", 'A')A
%pune adresse de pointeurprintf("%p", (void*)&x)0x7ffd...

Deux règles se cachent dans cet extrait. Un % littéral s'écrit %% - un % seul à la fin d'une chaîne de format est un comportement indéfini. Et %p exige un void *, alors convertissez le pointeur ; passer un int * directement est techniquement indéfini même si cela s'affiche très bien en pratique.

Le tableau complet - chaque spécificateur, chaque modificateur de longueur, la colonne scanf - est sur la page spécificateurs de format. Cette page s'en tient à ceux que vous utilisez.

Largeur, précision et alignement

Entre le % et la lettre, vous pouvez mettre des drapeaux, une largeur et une précision. C'est ainsi qu'on transforme une boucle en tableau.

La largeur est un minimum, jamais un maximum : %3d avec la valeur 123456 affiche les six chiffres. La précision signifie autre chose selon le type - chiffres après la virgule pour %f, caractères maximum pour %s, chiffres minimum pour %d.

Les deux peuvent être fournies à l'exécution avec un *, qui lit un argument int :

Changer width remet en page tout le tableau. C'est la façon standard d'afficher des colonnes alignées en C - aucune fonction de remplissage de chaîne nécessaire.

Afficher les flottants de façon sensée

%f sans précision affiche toujours six chiffres après la virgule, ce que personne ne veut vraiment :

Trois choix, et une règle empirique pour chacun :

  • %.Nf pour l'argent, les mesures, tout ce qui a un nombre naturel de décimales. %.2f est ce qu'il vous faut pour une monnaie.
  • %e pour la notation scientifique, toujours d.dddddde±dd.
  • %g quand vous ne connaissez pas l'ordre de grandeur à l'avance. Il choisit %f ou %e, le plus court des deux, et supprime les zéros terminaux - le choix par défaut le plus raisonnable pour journaliser une valeur que vous n'avez pas vue.

Un piège à énoncer clairement : il n'y a pas de %lf dans printf. Les arguments float sont promus en double avant même que printf ne les voie, donc %f gère les deux, et %lf n'a de sens que dans scanf, où la taille de la destination compte. (C99 accepte bien %lf dans printf comme synonyme, mais l'habitude de l'écrire pousse les gens à écrire %f dans scanf, ce qui casse réellement.)

Les entiers d'autres largeurs

%d est pour int. Les types plus grands ou non signés ont besoin d'un modificateur de longueur, et les mélanger est un comportement indéfini plutôt qu'une erreur d'arrondi :

%zu pour size_t est celui que l'on rate le plus souvent - sizeof renvoie un size_t, pas un int, donc printf("%d", sizeof x) est un bug qui fonctionne par hasard sur les systèmes 32 bits et déraille sur les 64 bits.

La valeur de retour

printf renvoie le nombre de caractères écrits, ou un nombre négatif en cas d'échec. Elle est généralement ignorée, et c'est le plus souvent sans conséquence - mais elle est réellement utile quand vous avez besoin de connaître la largeur de la sortie :

Les fonctions voisines sprintf et snprintf écrivent dans un tampon plutôt qu'à l'écran, et renvoient la longueur produite. Préférez toujours snprintf, qui prend la taille du tampon et ne peut pas déborder :

snprintf renvoie la longueur dont la chaîne complète aurait eu besoin, donc une valeur de retour égale ou supérieure à la taille du tampon vous dit que la sortie a été tronquée.

La vulnérabilité des chaînes de format

C'est le seul problème de sécurité de printf, et il est sévère. Ne passez jamais des données que vous n'avez pas écrites comme chaîne de format :

/* DANGEREUX - ne faites jamais cela */
printf(user_input);

/* Correct */
printf("%s", user_input);

Si user_input contient %s, printf lit un argument jamais passé et suit le garbage en forme de pointeur qu'il y trouve, plantant généralement. %x répété déverse le contenu de la pile à l'écran, ce qui peut inclure des mots de passe ou des clés. Et %n, qui écrit le compte de caractères dans un argument, a historiquement transformé cela en corruption mémoire arbitraire et en exécution de code à distance.

La règle est absolue et ne coûte rien : la chaîne de format est toujours un littéral que vous avez écrit. Tout ce qui est variable entre en argument. Le -Wformat-security de GCC signale l'erreur, et il est inclus dans -Wall -Wextra.

Le tamponnage : pourquoi la sortie arrive parfois en retard

printf n'écrit pas immédiatement dans le terminal. La sortie est tamponnée - vidée quand le tampon se remplit, quand un saut de ligne apparaît sur un terminal, ou quand le programme se termine normalement. Deux conséquences :

  • Un programme qui plante peut perdre de la sortie « déjà affichée ». Si vous déboguez avec printf et que le dernier message n'apparaît jamais, le plantage a eu lieu après cette ligne, pas avant. Ajoutez fflush(stdout) ou envoyez la sortie de débogage vers stderr, qui n'est pas tamponné.
  • fprintf(stderr, ...) est la bonne façon d'afficher des erreurs. Ce flux n'est pas tamponné et part vers un canal distinct que les utilisateurs peuvent rediriger indépendamment de la sortie normale.

Questions fréquentes

Comment fonctionne printf en C ?

printf prend une chaîne de format et la parcourt caractère par caractère. Les caractères ordinaires sont affichés tels quels ; chaque % commence une spécification de conversion qui consomme l'un des arguments suivants et l'affiche sous la forme demandée. printf("%d pommes\n", 5) affiche 5 pommes et un saut de ligne.

Comment afficher un flottant avec 2 décimales en C ?

Utilisez une précision : printf("%.2f\n", 3.14159) affiche 3.14. Le nombre après le point est le nombre de chiffres après la virgule. %f sans précision affiche toujours 6 chiffres, c'est pourquoi les flottants non formatés paraissent si bruyants.

Pourquoi printf affiche-t-il n'importe quoi ou plante-t-il ?

Presque toujours à cause d'une discordance entre le spécificateur et l'argument. printf("%d", 3.5) ou printf("%s", 42) est un comportement indéfini - printf ne peut pas voir les vrais types, il fait confiance à la chaîne de format. Compilez avec -Wall, qui fait vérifier à GCC le format par rapport aux arguments.

Pourquoi printf(saisie_utilisateur) est-il dangereux ?

Si le texte de l'utilisateur contient des séquences %, printf leur obéira et lira des arguments qui n'ont jamais été passés - fuite de mémoire de pile, et avec %n même écriture en mémoire. C'est la classique vulnérabilité des chaînes de format. Écrivez toujours printf("%s", saisie_utilisateur) à la place.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER