Specyfikator formatu mówi printf, jak zamienić wartość na tekst, a scanf, jak zamienić tekst na wartość. To jedyne miejsce w C, w którym kompilator domyślnie ci nie pomoże, bo funkcja nie widzi typów argumentów. Ta strona to tabela, z którą warto się porównywać.
Ogólny kształt specyfikacji wygląda tak:
%[flagi][szerokość][.precyzja][długość]konwersja
Wymagane są tylko % i litera konwersji. %-8.2lf używa wszystkich pięciu części.
Tabela konwersji
| Specyfikator | Znaczenie | Argument printf | Argument scanf |
|---|---|---|---|
%d | liczba całkowita ze znakiem, dziesiętnie | int | int * |
%i | liczba całkowita ze znakiem | int (jak %d) | int * (przyjmuje też 0x i ósemkowe z 0) |
%u | liczba bez znaku, dziesiętnie | unsigned int | unsigned int * |
%f | liczba zmiennoprzecinkowa, dziesiętnie | double | float * |
%F | jak %f, wielkie INF/NAN | double | - |
%e / %E | notacja naukowa | double | float * |
%g / %G | krótsza z %f / %e | double | float * |
%a / %A | szesnastkowa liczba zmiennoprzecinkowa | double | float * |
%c | pojedynczy znak | int (promowany char) | char * |
%s | łańcuch znaków | char * (zakończony '\0') | char * (bufor) |
%p | adres wskaźnika | void * | void ** |
%x / %X | liczba bez znaku, szesnastkowo | unsigned int | unsigned int * |
%o | liczba bez znaku, ósemkowo | unsigned int | unsigned int * |
%n | zapisuje liczbę dotąd wypisanych znaków | int * | int * |
%% | dosłowny znak % | brak | dopasowuje dosłowny % |
%[...] | zbiór znaków (tylko scanf) | - | char * |
Dwa wpisy wymagają ostrzeżenia. %n zapisuje do pamięci zamiast wypisywać i to na nim opiera się podatność format string: nigdy nie pozwól, żeby tekst od użytkownika trafił do łańcucha formatu. A %p wymaga void *; przekazanie int * bezpośrednio jest niezdefiniowane, choć każdy prawdziwy kompilator i tak wypisze adres.
Modyfikatory długości
Modyfikator długości stoi między precyzją a literą konwersji i mówi, jak duży jest naprawdę argument.
Konwersje ze znakiem (d i) i bez znaku (u x o) przyjmują różne typy: %lu oczekuje unsigned long, a nie long, a pomyłka to niezdefiniowane zachowanie, nie kosmetyczna wpadka.
| Modyfikator | Z d i (printf / scanf) | Z u x o (printf / scanf) |
|---|---|---|
hh | int (z signed char) / signed char * | unsigned int (z unsigned char) / unsigned char * |
h | int (z short) / short * | unsigned int (z unsigned short) / unsigned short * |
| (brak) | 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 | typ rozmiaru ze znakiem / wskaźnik na niego | size_t / size_t * |
j | intmax_t / intmax_t * | uintmax_t / uintmax_t * |
t | ptrdiff_t / ptrdiff_t * | odpowiednik ptrdiff_t bez znaku |
A dla rodzin zmiennoprzecinkowej i znakowej:
| Modyfikator | Z | Typ w printf | Typ w scanf |
|---|---|---|---|
| (brak) | f e g a | double | float * |
l | f e g a | double (akceptowany, bez efektu) | double * |
L | f e g a | long double | long double * |
l | c s | wint_t / wchar_t * | wchar_t * |
Kombinacje, które naprawdę się wpisuje:
%zu warto zapamiętać. sizeof, strlen i każdy rozmiar z biblioteki standardowej zwracają size_t, który w systemie 64-bitowym ma 8 bajtów, podczas gdy int ma 4. printf("%d", strlen(s)) to prawdziwy błąd, który na niektórych platformach pozornie działa. Używaj %zu.
Modyfikatory hh i h istnieją głównie dla scanf, gdzie liczy się rozmiar miejsca docelowego. W printf wartość short jest promowana do int przed wywołaniem, więc %d i tak wypisze ją poprawnie; %hd tylko dokumentuje intencję.
Typy o stałej szerokości
Dla typów z <stdint.h>, takich jak int32_t, przenośne specyfikatory to makra z <inttypes.h>:
PRId32 rozwija się do liter właściwych dla twojej platformy, a sąsiadujące literały łańcuchowe łączą się ze sobą, dlatego format jest podzielony na trzy kawałki. Wygląda to brzydko; alternatywa, czyli rzutowanie na long long i użycie %lld, jest często czytelniejsza i zawsze poprawna.
Flagi
Flagi stoją tuż po % i zmieniają sposób prezentacji. Dotyczą tylko printf.
| Flaga | Efekt | Przykład | Wynik |
|---|---|---|---|
- | dopełnia za wartością zamiast przed nią | %-6d| dla 42 | 42 | |
+ | zawsze wypisuje znak dla wartości ze znakiem | %+d dla 42 | +42 |
| spacja | wypisuje spację tam, gdzie byłby + | % d dla 42 | 42 |
0 | dopełnia zerami zamiast spacjami | %06d dla 42 | 000042 |
# | forma alternatywna: 0x dla %x, 0 dla %o, zachowuje kropkę dla %g | %#x dla 255 | 0xff |
Flagi można łączyć: %-+8.2f to minimalna szerokość 8, dwa miejsca po przecinku, zawsze ze znakiem i z dopełnieniem za liczbą. Jeśli napiszesz jednocześnie - i 0, wygrywa -.
Szerokość i precyzja
Szerokość to minimalny rozmiar pola: wynik jest dopełniany, żeby ją osiągnąć, i nigdy nie jest obcinany, żeby się w niej zmieścić. Precyzja (po kropce) znaczy coś innego dla każdej rodziny:
| Konwersja | Precyzja oznacza |
|---|---|
%f %e | cyfry po przecinku (domyślnie 6) |
%g | łączną liczbę cyfr znaczących (domyślnie 6) |
%s | maksymalną liczbę wypisanych znaków |
%d %i %u %x %o | minimalną liczbę cyfr, dopełnioną zerami |
* w miejscu którejkolwiek z tych liczb pobiera ją z argumentu int, odczytanego przed samą wartością. Tak buduje się tabele, których szerokości kolumn są obliczane w czasie działania programu.
W scanf szerokość ma inne i znacznie ważniejsze zadanie: ogranicza, ile danych wejściowych pochłonie konwersja, i tylko ona chroni %s przed przepełnieniem bufora.
Czym różnią się printf i scanf
Wyglądają podobnie, a nie zgadzają się w czterech miejscach. Każde z nich to realne źródło błędów:
| Sytuacja | printf | scanf |
|---|---|---|
wartość double | %f (float promowany do double) | %lf, bo %f oznacza float * |
| Szerokość | minimalny rozmiar pola, dopełnia | maksimum wczytanych danych, obcina |
| Argumenty | wartości | wskaźniki: &x albo nazwa tablicy |
| Spacja w formacie | wypisuje spację | pomija dowolny ciąg białych znaków |
Wypróbuj 2.5 hello. Format, który to wczytuje, i format, który to wypisuje, używają różnych liter dla tego samego double, a 15 w %15s ogranicza wejście do 15 znaków plus terminator, czyli dokładnie do rozmiaru word.
Zbiór znaków w scanf
%[...] wczytuje zbiór znaków zamiast słowa oddzielonego białymi znakami. Tak wczytuje się całą linię przez scanf, choć fgets pozostaje lepszym narzędziem:
[^\n] oznacza "dowolne znaki poza znakiem nowej linii". %[0-9] wczyta tylko cyfry, a %[abc] tylko te trzy litery. W przeciwieństwie do %s zbiór znaków nie pomija początkowych białych znaków, więc zabłąkany znak nowej linii w buforze sprawia, że nie dopasuje niczego.
Pomyłka to niezdefiniowane zachowanie
printf nie widzi prawdziwego typu argumentu. Odczytuje tyle bajtów, ile sugeruje specyfikator, i interpretuje je tak, jak on każe. Niezgodność nie jest więc usterką formatowania, tylko odczytem pamięci jako czegoś innego:
printf("%d\n", 3.5); /* wypisuje śmieci: czyta bity double jako int */
printf("%s\n", 42); /* traktuje 42 jak adres: awaria */
printf("%d\n", strlen(s)); /* size_t czytany jako int: błąd na 64 bitach */
printf("%f\n", 3); /* int czytany jako double: śmieci */
Obrona kosztuje jedną flagę. Kompiluj z -Wall, a GCC sprawdzi każdy dosłowny łańcuch formatu względem jego argumentów:
gcc -Wall -Wextra program.c -o program
Zgłosi każdą z czterech linii powyżej już podczas kompilacji. To najcenniejsze ostrzeżenie w C i w edytorze powyżej jest domyślnie włączone.
O praktycznej stronie korzystania z nich, czyli wyrównanych tabelach, czytelnym wypisywaniu liczb zmiennoprzecinkowych i wczytywaniu danych bez klasycznego błędu ze znakiem nowej linii, przeczytasz na stronach printf i scanf.
Najczęściej zadawane pytania
Co oznacza %d w C?
%d to specyfikator konwersji dla liczby całkowitej ze znakiem w zapisie dziesiętnym, czyli int. W printf wypisuje argument jako cyfry dziesiętne, a w scanf wczytuje cyfry dziesiętne do int *. %i znaczy w printf to samo, ale w scanf %i przyjmuje też szesnastkowe 0x i ósemkowe liczby z zerem na początku.
Czym różni się %f od %lf w C?
W printf praktycznie niczym: argumenty float są promowane do double, więc %f wypisuje oba typy, a C99 akceptuje %lf jako synonim. W scanf różnica jest kluczowa: %f zapisuje do float * (4 bajty), a %lf do double * (8 bajtów). Użycie %f dla zmiennej double niszczy pamięć.
Jak wypisać size_t albo long long w C?
%zu dla size_t (to zwracają sizeof i strlen) oraz %lld / %llu dla long long / unsigned long long. Użycie %d dla tych typów to niezdefiniowane zachowanie: przypadkiem działa na systemach 32-bitowych, a psuje się na 64-bitowych, dlatego ten błąd żyje tak długo.
Co się stanie, jeśli użyjesz złego specyfikatora formatu w C?
To niezdefiniowane zachowanie, a nie błąd zaokrąglenia. printf odczytuje argument jako to, co deklaruje specyfikator, więc printf("%d", 3.5) wypisuje śmieci, a printf("%s", 42) traktuje liczbę 42 jak adres i zwykle kończy się awarią. Kompiluj z -Wall, wtedy GCC sprawdza łańcuchy formatu względem argumentów.