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
| Specificatore | Significato | Argomento di printf | Argomento di scanf |
|---|---|---|---|
%d | intero decimale con segno | int | int * |
%i | intero con segno | int (come %d) | int * (accetta anche 0x e ottale con 0) |
%u | decimale senza segno | unsigned int | unsigned int * |
%f | virgola mobile decimale | double | float * |
%F | come %f, con INF/NAN maiuscoli | double | - |
%e / %E | notazione scientifica | double | float * |
%g / %G | il più corto tra %f e %e | double | float * |
%a / %A | virgola mobile esadecimale | double | float * |
%c | un singolo carattere | int (char promosso) | char * |
%s | stringa | char * (terminata da '\0') | char * (buffer) |
%p | indirizzo di un puntatore | void * | void ** |
%x / %X | esadecimale senza segno | unsigned int | unsigned int * |
%o | ottale senza segno | unsigned int | unsigned int * |
%n | salva i caratteri scritti fin lì | int * | int * |
%% | un % letterale | nessuno | corrisponde 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.
| Modificatore | Con d i (printf / scanf) | Con u x o (printf / scanf) |
|---|---|---|
hh | int (da signed char) / signed char * | unsigned int (da unsigned char) / unsigned char * |
h | int (da short) / short * | unsigned int (da unsigned short) / unsigned short * |
| (nessuno) | 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 | tipo dimensione con segno / puntatore a esso | size_t / size_t * |
j | intmax_t / intmax_t * | uintmax_t / uintmax_t * |
t | ptrdiff_t / ptrdiff_t * | controparte senza segno di ptrdiff_t |
E per le famiglie in virgola mobile e dei caratteri:
| Modificatore | Con | Tipo in printf | Tipo in scanf |
|---|---|---|---|
| (nessuno) | f e g a | double | float * |
l | f e g a | double (accettato, nessun effetto) | double * |
L | f e g a | long double | long double * |
l | c s | wint_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.
| Flag | Effetto | Esempio | Output |
|---|---|---|---|
- | riempie dopo il valore invece che prima | %-6d| con 42 | 42 | |
+ | stampa sempre il segno per i valori con segno | %+d con 42 | +42 |
| spazio | stampa uno spazio dove andrebbe un + | % d con 42 | 42 |
0 | riempie con zeri invece che con spazi | %06d con 42 | 000042 |
# | forma alternativa: 0x per %x, 0 per %o, mantiene il punto con %g | %#x con 255 | 0xff |
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:
| Conversione | La precisione indica |
|---|---|
%f %e | cifre dopo il punto decimale (6 di default) |
%g | cifre significative totali (6 di default) |
%s | numero massimo di caratteri da stampare |
%d %i %u %x %o | numero 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:
| Situazione | printf | scanf |
|---|---|---|
Valore double | %f (float promosso a double) | %lf: %f significa float * |
| Larghezza | dimensione minima del campo, riempie | massimo input consumato, tronca |
| Argomenti | valori | puntatori: &x, o il nome di un array |
| Uno spazio nel formato | stampa uno spazio | salta 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.