Menu

Specyfikatory formatu w C: pełna ściągawka printf i scanf

Wszystkie specyfikatory formatu w C w jednym miejscu: co znaczą %d, %s, %c, %f, %p i reszta, modyfikatory długości l, ll, h i z, szerokość, precyzja i flagi oraz to, czym printf różni się od scanf.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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

SpecyfikatorZnaczenieArgument printfArgument scanf
%dliczba całkowita ze znakiem, dziesiętnieintint *
%iliczba całkowita ze znakiemint (jak %d)int * (przyjmuje też 0x i ósemkowe z 0)
%uliczba bez znaku, dziesiętnieunsigned intunsigned int *
%fliczba zmiennoprzecinkowa, dziesiętniedoublefloat *
%Fjak %f, wielkie INF/NANdouble-
%e / %Enotacja naukowadoublefloat *
%g / %Gkrótsza z %f / %edoublefloat *
%a / %Aszesnastkowa liczba zmiennoprzecinkowadoublefloat *
%cpojedynczy znakint (promowany char)char *
%słańcuch znakówchar * (zakończony '\0')char * (bufor)
%padres wskaźnikavoid *void **
%x / %Xliczba bez znaku, szesnastkowounsigned intunsigned int *
%oliczba bez znaku, ósemkowounsigned intunsigned int *
%nzapisuje liczbę dotąd wypisanych znakówint *int *
%%dosłowny znak %brakdopasowuje 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.

ModyfikatorZ d i (printf / scanf)Z u x o (printf / scanf)
hhint (z signed char) / signed char *unsigned int (z unsigned char) / unsigned char *
hint (z short) / short *unsigned int (z unsigned short) / unsigned short *
(brak)int / int *unsigned int / unsigned int *
llong / long *unsigned long / unsigned long *
lllong long / long long *unsigned long long / unsigned long long *
ztyp rozmiaru ze znakiem / wskaźnik na niegosize_t / size_t *
jintmax_t / intmax_t *uintmax_t / uintmax_t *
tptrdiff_t / ptrdiff_t *odpowiednik ptrdiff_t bez znaku

A dla rodzin zmiennoprzecinkowej i znakowej:

ModyfikatorZTyp w printfTyp w scanf
(brak)f e g adoublefloat *
lf e g adouble (akceptowany, bez efektu)double *
Lf e g along doublelong double *
lc swint_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.

FlagaEfektPrzykładWynik
-dopełnia za wartością zamiast przed nią%-6d| dla 4242 |
+zawsze wypisuje znak dla wartości ze znakiem%+d dla 42+42
spacjawypisuje spację tam, gdzie byłby +% d dla 42 42
0dopełnia zerami zamiast spacjami%06d dla 42000042
#forma alternatywna: 0x dla %x, 0 dla %o, zachowuje kropkę dla %g%#x dla 2550xff

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:

KonwersjaPrecyzja oznacza
%f %ecyfry po przecinku (domyślnie 6)
%głączną liczbę cyfr znaczących (domyślnie 6)
%smaksymalną liczbę wypisanych znaków
%d %i %u %x %ominimalną 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:

Sytuacjaprintfscanf
wartość double%f (float promowany do double)%lf, bo %f oznacza float *
Szerokośćminimalny rozmiar pola, dopełniamaksimum wczytanych danych, obcina
Argumentywartościwskaźniki: &x albo nazwa tablicy
Spacja w formaciewypisuje 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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ