printf () без аргументов в C компилируется нормально. как?

я попробовал следующую программу c , и я ожидал получить ошибку времени компиляции, но почему компилятор не дает никакой ошибки?

#include <stdio.h>
int main(void)
{
    printf("%dn");
    return 0;
}

почему вывод зависит от компилятора? Вот вывод на различных компиляторах

вывод на Orwell Dev C++ IDE (использует gcc 4.8.1): 0

вывод на Visual C++, предоставленный Visual Studio 2010: 0

IDE CodeBlocks (использует gcc 4.7.1): значение мусора

онлайн-компилятор ideone.com : мусор значение

что здесь не так ?

10 ответов


ваша программа будет компилироваться нормально, как printf() является вариационной функцией, и проверка соответствия количества спецификаторов формата с предоставленным аргументом по умолчанию не выполняется.

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

в соответствии с главой 7.19.6.1, c99 стандартные (с fprintf())

если есть недостаточно аргументов для формата, поведение не определено.

если вы компилируете с помощью -Wformat флаг gcc, ваш компилятор выдаст предупреждение о несоответствии.


из-за того, как работают c variadic аргументы, компилятор не может отслеживать их правильное использование. По-прежнему (синтаксически) законно предоставлять меньше или больше параметров, которые функция должна работать, хотя это обычно заканчивается неопределенным поведением при взгляде на стандарт.

декларация printf выглядит так:

int printf(const char*, ...);

компилятор видит только ..., и знает, что может быть ноль или более дополнительных аргументов, которые функция может или можете не использовать. Вызываемая функция не знаю сколько аргументов он передан; он может, в лучшем случае,предположим что он прошел всю необходимую информацию и ничего больше.

сравните это с другими языками, такими как C#:

void WriteLine(string format, params object[] arguments);

здесь метод точно знает, сколько дополнительных аргументов было передано (doing arguments.Length).

В С ++ variadic функции и особенности printf являются частой причиной безопасности факторы уязвимости. Printf заканчивает чтение необработанных байтов из стека, что может привести к утечке важных сведений о вашем приложении и его среде безопасности.

по этой причине Clang и GCC поддерживают специальное расширение для проверки printf форматы. Если вы используете недопустимую строку формата, вы получите предупреждение (а не ошибку).

code.c:4:11: warning: more '%' conversions than data arguments [-Wformat]
    printf("%d\n");
           ~~^

это просто неопределенное поведение если вы не предоставите достаточных аргументов printf, что означает, что поведение непредсказуемо. От проект стандарта C99 раздел 7.19.6.1 функция fprintf, который также охватывает printf для этого случая:

если недостаточно аргументов для формата, поведение не определено.

С printf это вариативную функцию нет соответствия аргументов объявлению функции. Таким образом, компилятор должен поддерживать проверку строки формата поддержки, которая покрывается -флаг Wformat в gcc:

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

включение достаточного количества предупреждений компилятора важно, для этого кода gcc С помощью -Wall флаг говорит нам (посмотреть его в прямом эфире):

 warning: format '%d' expects a matching 'int' argument [-Wformat=]
 printf("%d\n");
 ^

это компилируется. Потому что он соответствует прототипу printf (), который является

printf(const char *,...);

во время выполнения вызова

printf("%d\n");

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


вы вызываете неопределенное поведение. Это ваша проблема, а не компилятора, и в основном все "разрешено".

конечно, практически каждый существующий компилятор должен иметь возможность предупредить вас об этом конкретном условии относительно printf(), вы просто должны позволить ему (путем включения и учета предупреждений компилятора).


в общих диагностических сообщениях (вы можете думать о них как об ошибках компиляции) не гарантируется для неопределено поведение (как отсутствие достаточных аргументов для printf вызов функции, как в вашем случае), которые не считаются нарушениями правил синтаксиса или ограничений.

C11 (N1570) §5.1.1.3 / p1 Диагностика:

соответствующая реализация должна производить по крайней мере один диагностический сообщение (идентифицировано в осуществление определенным образом), если ЕП предварительной обработки или преобразования содержит нарушение любое синтаксическое правило или ограничение, даже если поведение также явно указано как undefined или implementation-defined. Диагностические сообщения не нужно производить в других обстоятельствах.9)

другими словами, ваш компилятор может свободно переводить такой блок, но вы никогда не должны запускать его или полагаться на его поведение (поскольку оно де-факто непредсказуемо). Кроме того, вашему компилятору разрешено не предоставлять никакой документации (то есть стандарт C не применяет ее для этого), как это требуется для поведения, определенного реализацией или локали.

C11 (N1570) §3.4.3 / p2 неопределенное поведение:

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


используя g++ с параметром командной строки -Wall производит следующие диагностики:

g++ -Wall   -c -g -MMD -MP -MF "build/Debug/MinGW-Windows/main.o.d" -o build/Debug/MinGW-Windows/main.o main.cpp
main.cpp: In function 'int main(void)':
main.cpp:17:16: warning: format '%d' expects a matching 'int' argument [-Wformat=]
     printf("%d");
                ^

Это очень полезно, не так ли?

gcc/g++ также проверьте, действительно ли спецификаторы формата соответствуют типам параметров. Это действительно здорово для отладки.


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


какие предупреждения / сообщения Вы получили с помощью своих компиляторов? я запустил это через gcc (Ubuntu 4.8.2-19ubuntu1) и получил предупреждение

warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=]
     printf("%d\n");
     ^

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

что я думаю произойдет: сигнатура функции printf независимая из поведения скомпилированного кода. во время компиляции, все компилятор заботится о том, чтобы проверить, есть ли хотя бы один аргумент и продолжается. однако скомпилированная функция сначала проанализирует выражение формата и, в зависимости от этого, прочитает дальнейшие аргументы из стека аргументов функций. в нем он просто ожидает соответствующих значений (int, float и т. д.) и использует их. поэтому, если вы не укажете аргумент, место в стеке вызовов функций не зарезервировано, а printf по-прежнему считывает случайную память (в этом случае в первом месте). это также объясняет вывод" мусора", который будет отличаться каждый раз, когда вы вызываете двоичный файл. вы даже можете расширить код

#include <stdio.h>
int main(void)
{
    printf("%d\n%d\n");
    return 0;
}

и разные фигня чисел :)

затем снова это будет зависеть от среды / процесса / компилятора, значения которого будут считываться. "неопределенное поведение" - это то, что лучше всего описывает этот эффект, иногда нулевой, иногда другой.

Я надеюсь, что это проясняет ваш вопрос!


в контексте printf () и fprintf (), согласно стандартному C11 пункту 7.21.6.1, "если для формата недостаточно аргументов, поведение не определено. Если формат исчерпан, а аргументы остаются, избыточные аргументы оцениваются (как всегда), но в противном случае игнорируются."