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

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

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

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

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

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

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

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

Что здесь происходит не так?


person Destructor    schedule 28.01.2015    source источник
comment
Вы можете заставить свой компилятор сообщить вам об этом, включив предупреждения. Хотя эта программа компилируется, она будет искать соответствующий целочисленный аргумент, вызывая неопределенное поведение.   -  person Iharob Al Asimi    schedule 28.01.2015
comment
В этом прелесть неопределенного поведения :)   -  person pmg    schedule 28.01.2015


Ответы (10)


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

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

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

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

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

person Sourav Ghosh    schedule 28.01.2015
comment
Вы также можете упомянуть более общие параметры gcc -Wall и clang -Weverything. - person chqrlie; 28.08.2016

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

Объявление printf выглядит так:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это хорошо компилируется. Поскольку он соответствует прототипу printf(), который

printf(const char *,...);

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

printf("%d\n");

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

person Gopi    schedule 28.01.2015

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

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

person DevSolar    schedule 28.01.2015

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

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

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

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

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

ПРИМЕЧАНИЕ Возможное неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами до поведения во время трансляции или выполнения программы документированным образом, характерным для среды (с выдачей или без выдачи диагностического сообщения), до прекращения трансляции или выполнения (с выдача диагностического сообщения).

person Grzegorz Szpetkowski    schedule 28.01.2015

Использование 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++ также проверьте, действительно ли спецификаторы формата соответствуют типам параметров. Это действительно здорово для отладки.

person Axel Kemper    schedule 28.01.2015
comment
И как это отвечает на вопрос? - person Spikatrix; 28.01.2015
comment
Фрагмент кода компилируется нормально, если не включены предупреждения (см. комментарий @iharob). Проверка переменных параметров кажется сильной стороной g++/gcc и, вероятно, не поддерживается большинством других компиляторов. - person Axel Kemper; 28.01.2015
comment
Добавление -Werror сделает эти предупреждения фатальными и предотвратит компиляцию. Разумная предосторожность. - person chqrlie; 28.08.2016

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

person Codor    schedule 28.01.2015

какие предупреждения/сообщения вы получали при использовании ваших компиляторов? я запустил это через 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;
}

и получить два разных номера мусора :)

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

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

person daniel.wirtz    schedule 28.01.2015

В контексте printf() и fprintf(), в соответствии со стандартом C11, пункт 7.21.6.1, «Если для формата недостаточно аргументов, поведение не определено. Если формат исчерпан, а аргументы остаются, лишние аргументы удаляются. оцениваются (как всегда), но в остальном игнорируются».

person Karthik Sagar    schedule 27.08.2016