Menu

Specificatori di formato in C: guida completa a printf e scanf

Tutti gli specificatori di formato del C in un unico posto: cosa significano %d, %s, %c, %f, %p e gli altri, i modificatori di lunghezza l, ll, h e z, larghezza, precisione e flag, e quali cambiano tra printf e scanf.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Uno specificatore di formato dice a printf come trasformare un valore in testo, e a scanf come trasformare del testo in un valore. È l'unica parte del C in cui il compilatore di default non può aiutarti, perché i tipi degli argomenti sono invisibili alla funzione, quindi questa pagina è la tabella da tenere sottomano.

La forma generale di una specifica è:

%[flag][larghezza][.precisione][lunghezza]conversione

Sono obbligatori solo il % e la lettera di conversione. %-8.2lf usa tutte e cinque le parti.

La tabella delle conversioni

SpecificatoreSignificatoArgomento di printfArgomento di scanf
%dintero decimale con segnointint *
%iintero con segnoint (come %d)int * (accetta anche 0x e ottale con 0)
%udecimale senza segnounsigned intunsigned int *
%fvirgola mobile decimaledoublefloat *
%Fcome %f, con INF/NAN maiuscolidouble-
%e / %Enotazione scientificadoublefloat *
%g / %Gil più corto tra %f e %edoublefloat *
%a / %Avirgola mobile esadecimaledoublefloat *
%cun singolo carattereint (char promosso)char *
%sstringachar * (terminata da '\0')char * (buffer)
%pindirizzo di un puntatorevoid *void **
%x / %Xesadecimale senza segnounsigned intunsigned int *
%oottale senza segnounsigned intunsigned int *
%nsalva i caratteri scritti fin lìint *int *
%%un % letteralenessunocorrisponde a un % letterale
%[...]insieme di caratteri (solo scanf)-char *

Due voci meritano un avvertimento. %n scrive in memoria invece di stampare, ed è il meccanismo dietro la vulnerabilità delle stringhe di formato: non lasciare mai che il testo dell'utente arrivi in una stringa di formato. E %p richiede un void *; passare direttamente un int * è indefinito, anche se tutti i compilatori reali stampano comunque l'indirizzo.

Modificatori di lunghezza

Il modificatore di lunghezza sta tra la precisione e la lettera di conversione, e dice quanto è grande davvero l'argomento.

Le conversioni con segno (d i) e quelle senza segno (u x o) accettano tipi diversi: %lu vuole un unsigned long, non un long, e sbagliare qui è comportamento indefinito, non una svista estetica.

ModificatoreCon d i (printf / scanf)Con u x o (printf / scanf)
hhint (da signed char) / signed char *unsigned int (da unsigned char) / unsigned char *
hint (da short) / short *unsigned int (da unsigned short) / unsigned short *
(nessuno)int / int *unsigned int / unsigned int *
llong / long *unsigned long / unsigned long *
lllong long / long long *unsigned long long / unsigned long long *
ztipo dimensione con segno / puntatore a essosize_t / size_t *
jintmax_t / intmax_t *uintmax_t / uintmax_t *
tptrdiff_t / ptrdiff_t *controparte senza segno di ptrdiff_t

E per le famiglie in virgola mobile e dei caratteri:

ModificatoreConTipo in printfTipo in scanf
(nessuno)f e g adoublefloat *
lf e g adouble (accettato, nessun effetto)double *
Lf e g along doublelong double *
lc swint_t / wchar_t *wchar_t *

Le combinazioni che scrivi davvero:

%zu è quello da imparare a memoria. sizeof, strlen e qualsiasi dimensione della libreria standard restituiscono size_t, che occupa 8 byte su un sistema a 64 bit mentre int ne occupa 4. printf("%d", strlen(s)) è un bug reale che su alcune piattaforme sembra funzionare: usa %zu.

I modificatori hh e h esistono soprattutto per scanf, dove conta la dimensione della destinazione. In printf uno short viene promosso a int prima della chiamata, quindi %d lo stamperebbe correttamente comunque; %hd documenta solo l'intenzione.

Tipi a larghezza fissa

Per i tipi di <stdint.h> come int32_t, gli specificatori portabili sono macro di <inttypes.h>:

PRId32 si espande nelle lettere giuste per la tua piattaforma, e i letterali stringa adiacenti vengono concatenati, ed è per questo che il formato è spezzato in tre pezzi. È brutto; l'alternativa, convertire a long long e usare %lld, di solito è più leggibile ed è sempre corretta.

Flag

I flag vanno subito dopo il % e cambiano la presentazione. Valgono solo per printf.

FlagEffettoEsempioOutput
-riempie dopo il valore invece che prima%-6d| con 4242 |
+stampa sempre il segno per i valori con segno%+d con 42+42
spaziostampa uno spazio dove andrebbe un +% d con 42 42
0riempie con zeri invece che con spazi%06d con 42000042
#forma alternativa: 0x per %x, 0 per %o, mantiene il punto con %g%#x con 2550xff

I flag si possono combinare: %-+8.2f significa larghezza minima 8, due decimali, segno sempre presente, riempimento dopo il numero. Se scrivi sia - sia 0, vince -.

Larghezza e precisione

La larghezza è una dimensione minima del campo: l'output viene riempito fino a raggiungerla, e non viene mai troncato per starci dentro. La precisione (dopo un punto) significa una cosa diversa per ogni famiglia:

ConversioneLa precisione indica
%f %ecifre dopo il punto decimale (6 di default)
%gcifre significative totali (6 di default)
%snumero massimo di caratteri da stampare
%d %i %u %x %onumero minimo di cifre, riempito con zeri

Un * al posto di uno dei due numeri lo prende da un argomento int, letto prima del valore stesso. È così che costruisci tabelle con larghezze delle colonne calcolate a runtime.

In scanf la larghezza ha un compito diverso e molto più importante: limita quanto input consuma una conversione, ed è l'unica cosa che si frappone tra %s e un buffer overflow.

Dove printf e scanf si differenziano

Si somigliano ma non concordano in quattro punti. Ognuno di questi è una fonte concreta di bug:

Situazioneprintfscanf
Valore double%f (float promosso a double)%lf: %f significa float *
Larghezzadimensione minima del campo, riempiemassimo input consumato, tronca
Argomentivaloripuntatori: &x, o il nome di un array
Uno spazio nel formatostampa uno spaziosalta qualsiasi sequenza di spazi bianchi

Prova con 2.5 ciao. Il formato che lo legge e quello che lo stampa usano lettere diverse per lo stesso double, e il 15 di %15s limita l'input a 15 caratteri più il terminatore, esattamente la dimensione di word.

L'insieme di caratteri di scanf

%[...] legge un insieme di caratteri invece di una parola delimitata da spazi. È il modo per leggere una riga intera con scanf, anche se fgets resta lo strumento migliore:

[^\n] significa "qualsiasi carattere tranne un a capo". %[0-9] leggerebbe solo cifre, %[abc] solo quelle tre lettere. A differenza di %s, un insieme di caratteri non salta gli spazi iniziali, quindi un a capo rimasto nel buffer fa sì che non corrisponda a niente.

Sbagliare è comportamento indefinito

printf non può vedere il tipo reale del tuo argomento. Legge il numero di byte che lo specificatore implica, interpretati come dice lo specificatore. Quindi una discrepanza non è un difetto di formattazione: è leggere la memoria come se fosse un'altra cosa.

printf("%d\n", 3.5);        /* stampa spazzatura: legge i bit di un double come int */
printf("%s\n", 42);         /* tratta 42 come un indirizzo: crash */
printf("%d\n", strlen(s));  /* size_t letto come int: sbagliato a 64 bit */
printf("%f\n", 3);          /* int letto come double: spazzatura */

La difesa costa un flag. Compila con -Wall e GCC confronta ogni stringa di formato letterale con i suoi argomenti:

gcc -Wall -Wextra program.c -o program

Segnala ognuna delle quattro righe qui sopra in fase di compilazione. È l'avviso più utile di tutto il C, ed è attivo di default nell'editor qui sopra.

Per il lato pratico del loro uso, cioè tabelle allineate, decimali stampati in modo leggibile e lettura dell'input senza il classico bug dell'a capo, vedi printf e scanf.

Domande frequenti

Cosa significa %d in C?

%d è lo specificatore di conversione per un intero decimale con segno, cioè un int. In printf stampa l'argomento come cifre in base 10; in scanf legge cifre in base 10 dentro un int *. %i ha lo stesso significato in printf, ma in scanf %i accetta anche esadecimali con 0x e ottali con lo zero iniziale.

Qual è la differenza tra %f e %lf in C?

In printf non c'è differenza pratica: gli argomenti float vengono promossi a double, quindi %f stampa entrambi, e C99 accetta %lf come sinonimo. In scanf la differenza è fondamentale: %f scrive in un float * (4 byte) e %lf in un double * (8 byte). Usare %f per un double corrompe la memoria.

Come si stampa un size_t o un long long in C?

%zu per size_t (quello che restituiscono sizeof e strlen) e %lld / %llu per long long / unsigned long long. Usare %d per questi tipi è comportamento indefinito: per caso funziona sui sistemi a 32 bit e sbaglia su quelli a 64, ed è per questo che il bug sopravvive così a lungo.

Cosa succede se usi lo specificatore di formato sbagliato in C?

È comportamento indefinito, non un errore di arrotondamento. printf legge l'argomento come qualunque cosa dichiari lo specificatore, quindi printf("%d", 3.5) stampa valori senza senso e printf("%s", 42) segue il numero 42 come se fosse un indirizzo e di solito va in crash. Compila con -Wall, che fa confrontare a GCC le stringhe di formato con gli argomenti.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA