Menu

Спецификаторы формата в C: полный справочник по printf и scanf

Все спецификаторы формата C в одном месте: что означают %d, %s, %c, %f, %p и остальные, модификаторы длины l, ll, h и z, ширина, точность и флаги, а также чем printf отличается от scanf.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Спецификатор формата говорит printf, как превратить значение в текст, а scanf — как превратить текст в значение. Это единственная часть C, где компилятор по умолчанию не может вам помочь: типы аргументов для функции невидимы, — поэтому данная страница и есть та таблица, с которой стоит сверяться.

Общий вид спецификации такой:

%[флаги][ширина][.точность][длина]преобразование

Обязательны только % и буква преобразования. %-8.2lf использует все пять частей.

Таблица преобразований

СпецификаторЗначениеАргумент printfАргумент scanf
%dзнаковое десятичное целоеintint *
%iзнаковое целоеint (то же, что %d)int * (принимает также 0x, восьмеричное с 0)
%uбеззнаковое десятичноеunsigned intunsigned int *
%fдесятичное с плавающей точкойdoublefloat *
%Fкак %f, но INF/NAN заглавнымиdouble-
%e / %Eнаучная нотацияdoublefloat *
%g / %Gболее короткое из %f / %edoublefloat *
%a / %Aшестнадцатеричное с плавающей точкойdoublefloat *
%cодиночный символint (повышенный char)char *
%sстрокаchar * (с завершающим '\0')char * (буфер)
%pадрес указателяvoid *void **
%x / %Xбеззнаковое шестнадцатеричноеunsigned intunsigned int *
%oбеззнаковое восьмеричноеunsigned intunsigned int *
%nзаписать число выведенных символовint *int *
%%литеральный %нетсопоставляется с литеральным %
%[...]набор символов (только scanf)-char *

Две записи заслуживают предупреждения. %n пишет в память, а не печатает, и именно он лежит в основе уязвимости строки формата — никогда не позволяйте пользовательскому тексту попадать в строку формата. А %p требует void *; передача int * напрямую формально не определена, хотя любой реальный компилятор всё равно напечатает адрес.

Модификаторы длины

Модификатор длины ставится между точностью и буквой преобразования и говорит, насколько велик аргумент на самом деле.

Знаковые преобразования (d i) и беззнаковые (u x o) принимают разные типы: %lu хочет unsigned long, а не long, и ошибка здесь — это неопределённое поведение, а не косметическая небрежность.

МодификаторС d i (printf / scanf)С u x o (printf / scanf)
hhint (из signed char) / signed char *unsigned int (из unsigned char) / unsigned char *
hint (из short) / short *unsigned int (из unsigned short) / unsigned short *
(нет)int / int *unsigned int / unsigned int *
llong / long *unsigned long / unsigned long *
lllong long / long long *unsigned long long / unsigned long long *
zзнаковый аналог size_t / указатель на негоsize_t / size_t *
jintmax_t / intmax_t *uintmax_t / uintmax_t *
tptrdiff_t / ptrdiff_t *беззнаковый аналог ptrdiff_t

А для семейств чисел с плавающей точкой и символов:

МодификаторС чемТип в printfТип в scanf
(нет)f e g adoublefloat *
lf e g adouble (принимается, ни на что не влияет)double *
Lf e g along doublelong double *
lc swint_t / wchar_t *wchar_t *

Комбинации, которые вы действительно набираете:

%zu — тот случай, который стоит запомнить. sizeof, strlen и любой размер из стандартной библиотеки возвращают size_t, который на 64-битной системе занимает 8 байт, тогда как int — 4. printf("%d", strlen(s)) — настоящий баг, который на некоторых платформах кажется работающим; используйте %zu.

Модификаторы hh и h существуют в основном ради scanf, где важен размер приёмника. В printf short повышается до int ещё до вызова, поэтому %d напечатал бы его правильно и так; %hd лишь документирует намерение.

Типы фиксированной ширины

Для типов из <stdint.h>, таких как int32_t, переносимые спецификаторы — это макросы из <inttypes.h>:

PRId32 разворачивается в нужные для вашей платформы буквы, а соседние строковые литералы склеиваются — вот почему формат разбит на три куска. Выглядит это уродливо; альтернатива — привести значение к long long и использовать %lld — часто читабельнее и всегда корректна.

Флаги

Флаги ставятся сразу после % и меняют представление. Они применимы только к printf.

ФлагЭффектПримерВывод
-дополнять после значения, а не перед ним%-6d| для 4242 |
+всегда печатать знак у знаковых значений%+d для 42+42
пробелпечатать пробел там, где был бы +% d для 42 42
0дополнять нулями, а не пробелами%06d для 42000042
#альтернативная форма: 0x для %x, 0 для %o, сохранить точку у %g%#x для 2550xff

Флаги комбинируются: %-+8.2f — это минимальная ширина 8, два знака после точки, всегда со знаком, дополнение после числа. Если написать и -, и 0, побеждает -.

Ширина и точность

Ширина — это минимальный размер поля: вывод дополняется до неё и никогда не обрезается ради неё. Точность (после точки) означает разное для разных семейств:

ПреобразованиеТочность означает
%f %eцифр после десятичной точки (по умолчанию 6)
%gвсего значащих цифр (по умолчанию 6)
%sмаксимум символов для вывода
%d %i %u %x %oминимум цифр, дополняется нулями

* вместо любого из этих чисел берёт его из аргумента типа int, который читается перед самим значением. Именно так строят таблицы, ширина столбцов которых вычисляется во время выполнения.

В scanf у ширины другая и гораздо более важная роль: она ограничивает количество поглощаемого ввода, и это единственное, что стоит между %s и переполнением буфера.

Чем printf и scanf различаются

Они выглядят похоже и расходятся в четырёх местах. Каждое из них — живой источник багов:

Ситуацияprintfscanf
значение double%f (float повышается до double)%lf%f означает float *
Ширинаминимальный размер поля, дополняетмаксимум поглощаемого ввода, обрезает
Аргументызначенияуказатели&x или имя массива
Пробел в строке форматапечатает пробелпропускает любую череду пробельных символов

Попробуйте 2.5 привет. Формат, который его читает, и формат, который его печатает, используют разные буквы для одного и того же double, а 15 в %15s ограничивает ввод пятнадцатью символами плюс завершающий ноль — ровно размер word.

Набор символов в scanf

%[...] читает набор символов, а не слово, ограниченное пробелами. Это способ прочитать целую строку через scanf, хотя fgets остаётся лучшим инструментом:

[^\n] означает «любые символы, кроме перевода строки». %[0-9] читал бы только цифры, %[abc] — только эти три буквы. В отличие от %s, набор символов не пропускает ведущие пробельные символы, поэтому оставшийся в буфере перевод строки приведёт к тому, что он не сопоставится вообще ни с чем.

Ошибка здесь — это неопределённое поведение

printf не видит настоящего типа вашего аргумента. Он читает ровно столько байтов, сколько подразумевает спецификатор, и интерпретирует их так, как тот говорит. Поэтому несовпадение — это не сбой форматирования, а чтение памяти не как того, чем она является:

printf("%d\n", 3.5);        /* печатает мусор: биты double читаются как int */
printf("%s\n", 42);         /* трактует 42 как адрес - падение */
printf("%d\n", strlen(s));  /* size_t читается как int - неверно на 64 битах */
printf("%f\n", 3);          /* int читается как double - мусор */

Защита стоит одного флага. Компилируйте с -Wall — и GCC сверит каждую литеральную строку формата с её аргументами:

gcc -Wall -Wextra program.c -o program

Он сообщит о каждой из четырёх строк выше ещё на этапе компиляции. Это самое ценное предупреждение в C, и в редакторе выше оно включено по умолчанию.

Практическую сторону — выровненные таблицы, читаемый вывод чисел с плавающей точкой, чтение ввода без классического бага с переводом строки — смотрите в статьях printf и scanf.

Часто задаваемые вопросы

Что означает %d в C?

%d — спецификатор преобразования для знакового десятичного целого, то есть для int. В printf он печатает аргумент десятичными цифрами; в scanf он читает десятичные цифры в int *. В printf %i означает то же самое, но в scanf %i дополнительно принимает шестнадцатеричную запись 0x и восьмеричную с ведущим нулём.

В чём разница между %f и %lf в C?

В printf практической разницы нет: аргументы типа float повышаются до double, поэтому %f печатает и то и другое, а C99 принимает %lf как синоним. В scanf разница критична: %f пишет в float * (4 байта), а %lf — в double * (8 байт). Использование %f для double портит память.

Как вывести size_t или long long в C?

%zu для size_t (то, что возвращают sizeof и strlen) и %lld / %llu для long long / unsigned long long. Использование %d для них — неопределённое поведение: на 32-битных системах оно случайно работает, а на 64-битных ломается, поэтому такой баг живёт очень долго.

Что будет, если использовать неверный спецификатор формата в C?

Это неопределённое поведение, а не ошибка округления. printf читает аргумент так, как заявляет спецификатор, поэтому printf("%d", 3.5) печатает мусор, а printf("%s", 42) трактует число 42 как адрес и обычно приводит к падению. Компилируйте с -Wall — тогда GCC сверяет строки формата с аргументами.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ