Спецификатор формата говорит printf, как превратить значение в текст, а scanf — как превратить текст в значение. Это единственная часть C, где компилятор по умолчанию не может вам помочь: типы аргументов для функции невидимы, — поэтому данная страница и есть та таблица, с которой стоит сверяться.
Общий вид спецификации такой:
%[флаги][ширина][.точность][длина]преобразование
Обязательны только % и буква преобразования. %-8.2lf использует все пять частей.
Таблица преобразований
| Спецификатор | Значение | Аргумент printf | Аргумент scanf |
|---|---|---|---|
%d | знаковое десятичное целое | int | int * |
%i | знаковое целое | int (то же, что %d) | int * (принимает также 0x, восьмеричное с 0) |
%u | беззнаковое десятичное | unsigned int | unsigned int * |
%f | десятичное с плавающей точкой | double | float * |
%F | как %f, но INF/NAN заглавными | double | - |
%e / %E | научная нотация | double | float * |
%g / %G | более короткое из %f / %e | double | float * |
%a / %A | шестнадцатеричное с плавающей точкой | double | float * |
%c | одиночный символ | int (повышенный char) | char * |
%s | строка | char * (с завершающим '\0') | char * (буфер) |
%p | адрес указателя | void * | void ** |
%x / %X | беззнаковое шестнадцатеричное | unsigned int | unsigned int * |
%o | беззнаковое восьмеричное | unsigned int | unsigned 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) |
|---|---|---|
hh | int (из signed char) / signed char * | unsigned int (из unsigned char) / unsigned char * |
h | int (из short) / short * | unsigned int (из unsigned short) / unsigned short * |
| (нет) | 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 | знаковый аналог size_t / указатель на него | size_t / size_t * |
j | intmax_t / intmax_t * | uintmax_t / uintmax_t * |
t | ptrdiff_t / ptrdiff_t * | беззнаковый аналог ptrdiff_t |
А для семейств чисел с плавающей точкой и символов:
| Модификатор | С чем | Тип в printf | Тип в scanf |
|---|---|---|---|
| (нет) | f e g a | double | float * |
l | f e g a | double (принимается, ни на что не влияет) | double * |
L | f e g a | long double | long double * |
l | c s | wint_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| для 42 | 42 | |
+ | всегда печатать знак у знаковых значений | %+d для 42 | +42 |
| пробел | печатать пробел там, где был бы + | % d для 42 | 42 |
0 | дополнять нулями, а не пробелами | %06d для 42 | 000042 |
# | альтернативная форма: 0x для %x, 0 для %o, сохранить точку у %g | %#x для 255 | 0xff |
Флаги комбинируются: %-+8.2f — это минимальная ширина 8, два знака после точки, всегда со знаком, дополнение после числа. Если написать и -, и 0, побеждает -.
Ширина и точность
Ширина — это минимальный размер поля: вывод дополняется до неё и никогда не обрезается ради неё. Точность (после точки) означает разное для разных семейств:
| Преобразование | Точность означает |
|---|---|
%f %e | цифр после десятичной точки (по умолчанию 6) |
%g | всего значащих цифр (по умолчанию 6) |
%s | максимум символов для вывода |
%d %i %u %x %o | минимум цифр, дополняется нулями |
* вместо любого из этих чисел берёт его из аргумента типа int, который читается перед самим значением. Именно так строят таблицы, ширина столбцов которых вычисляется во время выполнения.
В scanf у ширины другая и гораздо более важная роль: она ограничивает количество поглощаемого ввода, и это единственное, что стоит между %s и переполнением буфера.
Чем printf и scanf различаются
Они выглядят похоже и расходятся в четырёх местах. Каждое из них — живой источник багов:
| Ситуация | printf | scanf |
|---|---|---|
значение 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 сверяет строки формата с аргументами.