Почему printf не может правильно обрабатывать флаги, ширину поля и точность?
Я пытаюсь раскрыть все возможности printf, и я попробовал это:
printf("Test:%+*0d", 10, 20);
что печатает
Тест:%+100d
Я должен сначала использовать флаг +
тогда ширина *
и повторно использовать флаг 0
,
Почему это сделать этот вывод? Я специально использовал printf()
плохо, но мне интересно, почему он показывает мне число 100?
3 ответа
Это потому, что вы предоставляете синтаксическую бессмыслицу компилятору, поэтому он может делать все, что захочет. Связанное чтение, неопределенное поведение.
Скомпилируйте ваш код с включенными предупреждениями, и он скажет вам что-то вроде
предупреждение: неизвестный символ преобразования типа '0' в формате [-Wformat=]
printf("Test:%+*0d", 10, 20);
^
Чтобы быть правильным, утверждение должно быть одним из
printf("Test:%+*.0d", 10, 20); // note the '.'
где
0
используется как точностьРодственные, цитируя
C11
Глава §7.21.6.1, (выделено мной)Дополнительная точность, которая дает минимальное количество цифр для отображения
d
,i
,o
,u
,x
, а такжеX
преобразования, количество цифр после знака десятичной запятой дляa
,A
,e
,E
,f
, а такжеF
конверсии, максимальное количество значащих цифр дляg
а такжеG
число преобразований или максимальное число байтов, которое будет записано для s преобразований. Точность принимает форму периода (.
) следует либо звездочка*
(описано позже) или необязательным десятичным целым числом; если указан только период, точность принимается равной нулю. Если точность появляется с любым другим спецификатором преобразования, поведение не определено.printf("Test:%+0*d", 10, 20);
где
0
используется в качестве флага. В соответствии с синтаксисом все флаги должны отображаться вместе, перед любой другой записью спецификации преобразования вы не можете просто поместить ее где-либо в спецификации преобразования и ожидать, что компилятор будет следовать вашим намерениям.Опять же, чтобы процитировать, (и мой акцент)
Каждая спецификация преобразования представлена символом
%
, После%
в последовательности появятся:- Ноль или более флагов (в любом порядке) [...]
- Необязательная минимальная ширина поля [...]
- Дополнительная точность [...]
- Дополнительный модификатор длины [...]
- Спецификатор преобразования [....]
Ваш printf
неверный формат: флаги должны предшествовать спецификатору ширины.
После этого обрабатывает *
как спецификатор ширины, printf
ожидает либо .
или модификатор длины или спецификатор преобразования, 0
будучи ни один из них, поведение не определено.
Реализация вашей библиотеки printf
делает что-то странное, кажется, справиться *
заменив его фактическим аргументом ширины... Побочный эффект реализации. Другие могут делать что-то еще, в том числе прерывать программу. Такая ошибка формата будет особенно рискованной, если за ней %s
преобразование.
Изменение вашего кода на printf("Test:%+0*d", 10, 20);
должен дать ожидаемый результат:
Test:+000000020
В дополнение к ответу Сурава Гоша; важное понятие - неопределенное поведение, что сложно. Обязательно прочитайте блог Латтнера: что должен знать каждый программист на Си о неопределенном поведении. Смотрите также это.
Таким образом, намеренно (или, возможно, в зависимости от) какое-то неопределенное поведение в вашем коде является преднамеренной халатностью. Не делай этого. В очень редких случаях, когда вы хотите это сделать (я ничего не вижу), пожалуйста, задокументируйте это и обоснуйте себя в некоторых комментариях.
Имейте в виду, что если действительно printf
реализуется стандартной библиотекой C, она может (и часто) специально обрабатывается компилятором (с помощью GCC и GNU libc, что может произойти с помощью внутреннего) __builtin_printf
)
Стандарты C99 и C11 частично определяют поведение printf
но оставляет некоторые неопределенные случаи поведения, чтобы облегчить реализацию. Вы вряд ли полностью поймете или сможете подражать этим случаям. И сама реализация может измениться (например, на моем Debian Linux, обновление libc
может изменить неопределенное поведение printf
)
Если вы хотите понять больше printf
изучить источник реализации стандартной библиотеки C (например, http://musl-libc.org/, чей код вполне читабелен) и реализации GCC (в предположении операционной системы Linux).
Но сопровождающие GNU libc и GCC (и даже ядра Linux, через системные вызовы) могут свободно изменять неопределенное поведение (printf
и еще что-нибудь)
На практике всегда компилируем с gcc -Wall
(и, вероятно, также -g
) при использовании GCC. Не принимайте никаких предупреждений (поэтому улучшайте свой собственный код, пока не получите ни одного).