Un spécificateur de format dit à printf comment transformer une valeur en texte, et à scanf comment transformer du texte en valeur. C'est la seule partie du C où le compilateur ne peut pas vous aider par défaut - les types des arguments sont invisibles pour la fonction - donc cette page est le tableau à consulter.
La forme générale d'une spécification est :
%[drapeaux][largeur][.precision][longueur]conversion
Seuls le % et la lettre de conversion sont obligatoires. %-8.2lf utilise les cinq parties.
Le tableau des conversions
| Spécificateur | Signification | Argument printf | Argument scanf |
|---|---|---|---|
%d | entier décimal signé | int | int * |
%i | entier signé | int (comme %d) | int * (accepte aussi 0x, octal en 0) |
%u | décimal non signé | unsigned int | unsigned int * |
%f | flottant décimal | double | float * |
%F | comme %f, INF/NAN en majuscules | double | - |
%e / %E | notation scientifique | double | float * |
%g / %G | le plus court de %f / %e | double | float * |
%a / %A | flottant hexadécimal | double | float * |
%c | caractère unique | int (char promu) | char * |
%s | chaîne | char * (terminée par '\0') | char * (tampon) |
%p | adresse de pointeur | void * | void ** |
%x / %X | hexadécimal non signé | unsigned int | unsigned int * |
%o | octal non signé | unsigned int | unsigned int * |
%n | stocke le nombre de caractères écrits | int * | int * |
%% | un % littéral | aucun | correspond à un % littéral |
%[...] | ensemble de caractères (scanf seulement) | - | char * |
Deux entrées méritent un avertissement. %n écrit en mémoire au lieu d'afficher, et c'est le mécanisme derrière la vulnérabilité des chaînes de format - ne laissez jamais du texte utilisateur atteindre une chaîne de format. Et %p exige un void * ; passer un int * directement est indéfini même si tous les vrais compilateurs affichent quand même l'adresse.
Les modificateurs de longueur
Le modificateur de longueur se place entre la précision et la lettre de conversion, et dit quelle est la vraie taille de l'argument.
Les conversions signées (d i) et non signées (u x o) prennent des types différents - %lu attend un unsigned long, pas un long, et se tromper là-dessus est un comportement indéfini, pas une approximation cosmétique.
| Modificateur | Avec d i (printf / scanf) | Avec u x o (printf / scanf) |
|---|---|---|
hh | int (depuis signed char) / signed char * | unsigned int (depuis unsigned char) / unsigned char * |
h | int (depuis short) / short * | unsigned int (depuis unsigned short) / unsigned short * |
| (aucun) | int / int * | unsigned int / unsigned int * |
l | long / long * | unsigned long / unsigned long * |
ll | long long / long long * | unsigned long long / unsigned long long * |
z | type taille signé / pointeur sur ce type | size_t / size_t * |
j | intmax_t / intmax_t * | uintmax_t / uintmax_t * |
t | ptrdiff_t / ptrdiff_t * | homologue non signé de ptrdiff_t |
Et pour les familles flottante et caractère :
| Modificateur | Avec | Type printf | Type scanf |
|---|---|---|---|
| (aucun) | f e g a | double | float * |
l | f e g a | double (accepté, sans effet) | double * |
L | f e g a | long double | long double * |
l | c s | wint_t / wchar_t * | wchar_t * |
Les combinaisons que vous tapez réellement :
%zu est celui à mémoriser. sizeof, strlen et toutes les tailles de la bibliothèque standard renvoient un size_t, qui fait 8 octets sur un système 64 bits alors qu'int en fait 4. printf("%d", strlen(s)) est un vrai bug qui semble fonctionner sur certaines plateformes - utilisez %zu.
Les modificateurs hh et h existent surtout pour scanf, où la taille de destination compte. Dans printf, un short est promu en int avant l'appel, donc %d l'afficherait correctement de toute façon ; %hd ne fait que documenter l'intention.
Les types de largeur fixe
Pour les types de <stdint.h> comme int32_t, les spécificateurs portables sont des macros de <inttypes.h> :
PRId32 se développe en les bonnes lettres pour votre plateforme, et les littéraux de chaîne adjacents se concatènent, ce qui explique que le format soit coupé en trois morceaux. C'est laid ; l'alternative - convertir en long long et utiliser %lld - est souvent plus lisible et toujours correcte.
Les drapeaux
Les drapeaux se placent immédiatement après le % et modifient la présentation. Ils ne s'appliquent qu'à printf.
| Drapeau | Effet | Exemple | Sortie |
|---|---|---|---|
- | remplir après la valeur plutôt qu'avant | %-6d| sur 42 | 42 | |
+ | toujours afficher un signe pour les valeurs signées | %+d sur 42 | +42 |
| espace | afficher un espace là où irait un + | % d sur 42 | 42 |
0 | remplir avec des zéros plutôt que des espaces | %06d sur 42 | 000042 |
# | forme alternative : 0x pour %x, 0 pour %o, garder le point pour %g | %#x sur 255 | 0xff |
Les drapeaux se combinent : %-+8.2f signifie largeur minimale 8, deux décimales, toujours signé, rempli après le nombre. - l'emporte sur 0 si vous écrivez les deux.
Largeur et précision
La largeur est une taille de champ minimale - la sortie est complétée pour l'atteindre, et jamais tronquée pour y tenir. La précision (après un point) signifie autre chose pour chaque famille :
| Conversion | La précision signifie |
|---|---|
%f %e | chiffres après la virgule (6 par défaut) |
%g | total de chiffres significatifs (6 par défaut) |
%s | nombre maximal de caractères à afficher |
%d %i %u %x %o | nombre minimal de chiffres, complété par des zéros |
Un * à la place de l'un des nombres le prend dans un argument int, lu avant la valeur elle-même. C'est ainsi qu'on construit des tableaux dont les largeurs de colonnes sont calculées à l'exécution.
Dans scanf, la largeur a un rôle différent et bien plus important : elle limite ce qu'une conversion consommera, ce qui est la seule chose qui sépare %s d'un débordement de tampon.
Là où printf et scanf diffèrent
Ils se ressemblent et divergent en quatre points. Chacun est une source de bugs bien vivante :
| Situation | printf | scanf |
|---|---|---|
Valeur double | %f (float promu en double) | %lf - %f signifie float * |
| Largeur | taille de champ minimale, complète | entrée maximale consommée, tronque |
| Arguments | des valeurs | des pointeurs - &x, ou un nom de tableau |
| Un espace dans le format | affiche un espace | saute toute suite d'espaces |
Essayez 2.5 hello. Le format qui le lit et le format qui l'affiche utilisent des lettres différentes pour le même double, et le 15 de %15s plafonne l'entrée à 15 caractères plus le terminateur - exactement la taille de word.
L'ensemble de caractères de scanf
%[...] lit un ensemble de caractères plutôt qu'un mot délimité par des espaces. C'est la façon de lire une ligne entière avec scanf, même si fgets reste le meilleur outil :
[^\n] signifie « tous les caractères sauf un saut de ligne ». %[0-9] ne lirait que des chiffres, %[abc] que ces trois lettres. Contrairement à %s, un scanset ne saute pas les espaces initiaux, donc un saut de ligne égaré dans le tampon lui fait ne rien correspondre du tout.
Se tromper est un comportement indéfini
printf ne peut pas voir le vrai type de votre argument. Il lit le nombre d'octets que le spécificateur implique, interprétés comme le spécificateur le dit. Une discordance n'est donc pas un pépin de mise en forme - c'est lire de la mémoire comme la mauvaise chose :
printf("%d\n", 3.5); /* affiche du garbage : lit les bits d'un double comme un int */
printf("%s\n", 42); /* traite 42 comme une adresse - plantage */
printf("%d\n", strlen(s)); /* size_t lu comme int - faux en 64 bits */
printf("%f\n", 3); /* int lu comme double - garbage */
La défense coûte une option. Compilez avec -Wall et GCC vérifie chaque chaîne de format littérale par rapport à ses arguments :
gcc -Wall -Wextra program.c -o program
Il signale chacune des quatre lignes ci-dessus à la compilation. C'est l'avertissement le plus rentable du C, et il est actif par défaut dans l'éditeur ci-dessus.
Pour le côté pratique de leur usage - tableaux alignés, affichage lisible des flottants, lecture d'entrée sans le classique bug du saut de ligne - voir printf et scanf.
Questions fréquentes
Que signifie %d en C ?
%d est le spécificateur de conversion d'un entier décimal signé - un int. Dans printf il affiche l'argument en chiffres base 10 ; dans scanf il lit des chiffres base 10 dans un int *. %i signifie la même chose dans printf, mais dans scanf %i accepte aussi l'hexadécimal 0x et l'octal à zéro initial.
Quelle est la différence entre %f et %lf en C ?
Dans printf il n'y a pas de différence pratique : les arguments float sont promus en double, donc %f affiche les deux, et C99 accepte %lf comme synonyme. Dans scanf la différence est critique - %f écrit dans un float * (4 octets) et %lf dans un double * (8 octets). Utiliser %f pour un double corrompt la mémoire.
Comment afficher un size_t ou un long long en C ?
%zu pour size_t (ce que renvoient sizeof et strlen) et %lld / %llu pour long long / unsigned long long. Utiliser %d pour ceux-ci est un comportement indéfini - cela fonctionne par hasard sur les systèmes 32 bits et déraille sur les 64 bits, ce qui explique la longévité du bug.
Que se passe-t-il si on utilise le mauvais spécificateur de format en C ?
C'est un comportement indéfini, pas une erreur d'arrondi. printf lit l'argument comme le prétend le spécificateur, donc printf("%d", 3.5) affiche du garbage et printf("%s", 42) suit le nombre 42 comme une adresse et plante généralement. Compilez avec -Wall, qui fait vérifier à GCC les chaînes de format par rapport aux arguments.