Почему 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. Не принимайте никаких предупреждений (поэтому улучшайте свой собственный код, пока не получите ни одного).

Другие вопросы по тегам