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
Това е красотата на Undefined Behaviour :)   -  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 Standard не го налага да го прави), както е необходимо за поведение, дефинирано от изпълнението или специфично за локала.

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(), съгласно C стандарт C11 клауза 7.21.6.1, "Ако няма достатъчно аргументи за формата, поведението е недефинирано. Ако форматът е изчерпан, докато аргументите остават, излишните аргументи се се оценяват (както винаги), но иначе се игнорират."

person Karthik Sagar    schedule 27.08.2016