Menu

Les spécificateurs de format en C : la référence complète printf et scanf

Tous les spécificateurs de format du C au même endroit - ce que signifient %d, %s, %c, %f, %p et les autres, les modificateurs de longueur l, ll, h et z, largeur, précision et drapeaux, et ceux qui diffèrent entre printf et scanf.

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

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écificateurSignificationArgument printfArgument scanf
%dentier décimal signéintint *
%ientier signéint (comme %d)int * (accepte aussi 0x, octal en 0)
%udécimal non signéunsigned intunsigned int *
%fflottant décimaldoublefloat *
%Fcomme %f, INF/NAN en majusculesdouble-
%e / %Enotation scientifiquedoublefloat *
%g / %Gle plus court de %f / %edoublefloat *
%a / %Aflottant hexadécimaldoublefloat *
%ccaractère uniqueint (char promu)char *
%schaînechar * (terminée par '\0')char * (tampon)
%padresse de pointeurvoid *void **
%x / %Xhexadécimal non signéunsigned intunsigned int *
%ooctal non signéunsigned intunsigned int *
%nstocke le nombre de caractères écritsint *int *
%%un % littéralaucuncorrespond à 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.

ModificateurAvec d i (printf / scanf)Avec u x o (printf / scanf)
hhint (depuis signed char) / signed char *unsigned int (depuis unsigned char) / unsigned char *
hint (depuis short) / short *unsigned int (depuis unsigned short) / unsigned short *
(aucun)int / int *unsigned int / unsigned int *
llong / long *unsigned long / unsigned long *
lllong long / long long *unsigned long long / unsigned long long *
ztype taille signé / pointeur sur ce typesize_t / size_t *
jintmax_t / intmax_t *uintmax_t / uintmax_t *
tptrdiff_t / ptrdiff_t *homologue non signé de ptrdiff_t

Et pour les familles flottante et caractère :

ModificateurAvecType printfType scanf
(aucun)f e g adoublefloat *
lf e g adouble (accepté, sans effet)double *
Lf e g along doublelong double *
lc swint_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.

DrapeauEffetExempleSortie
-remplir après la valeur plutôt qu'avant%-6d| sur 4242 |
+toujours afficher un signe pour les valeurs signées%+d sur 42+42
espaceafficher un espace là où irait un +% d sur 42 42
0remplir avec des zéros plutôt que des espaces%06d sur 42000042
#forme alternative : 0x pour %x, 0 pour %o, garder le point pour %g%#x sur 2550xff

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 :

ConversionLa précision signifie
%f %echiffres après la virgule (6 par défaut)
%gtotal de chiffres significatifs (6 par défaut)
%snombre maximal de caractères à afficher
%d %i %u %x %onombre 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 :

Situationprintfscanf
Valeur double%f (float promu en double)%lf - %f signifie float *
Largeurtaille de champ minimale, complèteentrée maximale consommée, tronque
Argumentsdes valeursdes pointeurs - &x, ou un nom de tableau
Un espace dans le formataffiche un espacesaute 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.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER