2010-07-05 2 views
7
#include<stdio.h> 
int f(); 

int main() 
{ 

    f(1); 
    f(1,2); 
    f(1,2,3); 
} 

f(int i,int j,int k) 
{ 

    printf("%d %d %d",i,j,k); 

} 

он работает нормально (без каких-либо ошибок) ... может у PLZ объяснить, как он выполняется? как f (1) и f (1,2) ссылаются на f (int, int, int)?Пожалуйста, объясните, как работает prog

+2

, что вы делаете это черный C магии ;-) , какой компилятор вы используете для составления этого? – Vanya

+0

Похоже, вы принесли C# 4.0 на C. –

+0

Эта программа * не работает. Если это «отлично работает» в вашем случае, у вас, вероятно, есть довольно неортодоксальная идея «нормально работать». – AnT

ответ

14

У меня должно быть другое определение «error» :-) Что будет напечатано в первые два раза, когда вы вызываете свою функцию f? Я получаю

1 -1216175936 134513787 
1 2   134513787 
1 2   3 

для моих трех вызовов функций.

То, что вы видите, - это сдерживание с самых ранних дней C, когда люди играли без присмотра и без присмотра со своими функциональными вызовами.

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

Это совершенно компилируемое, хотя и очень неразумно C. И я имею в виду, что в очень реальном «неопределенном поведении» смысл слова (обращаясь конкретно к C99: «Если выражение, которое обозначает вызываемую функцию, имеет тип, который не включает прототип, ... если количество аргументов не равно числу параметров, поведение не определено »).

Вы действительно должны обеспечить полностью сформированные прототипы функций, таких как:

void f(int,int,int); 

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


Как и в сторону, что обычно происходит под одеялом, что функция вызова начинается со стеком как:

12345678 
11111111 

и толкает (к примеру) два значения в стек, так что в конце концов, как:

12345678 
11111111 
2 
1 

Когда вызываемая функция использует первые три значения в стеке (так как это то, что он хочет), он приходит к выводу, что она имеет 1, 2 и 11111111.

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

+0

afaik 'f()' is __still__, описанный в стандарте, поэтому он прекрасен (и на самом деле он компилируется только с одним предупреждением с помощью '-std = c99 -pedantic', и предупреждение не об этом); virtual -1 за то, что не объяснил, почему он __works__ – ShinTakezou

+0

Ну, да, это все еще _valid_, но так же 'setjmp' и' longjmp' - это не обязательно делает их хорошими _idea_ :-) В любом случае я добавил немного о том, почему он, скорее всего, работает. – paxdiablo

+0

Вы цитируете неправильный бит spec-f() - это функция без прототипа, а не функция с переменным числом аргументов, и ее определение не указывает, что оно принимает переменное количество аргументов - в разделе C99 6.5. 2.2 «Если выражение, которое обозначает вызываемую функцию, имеет тип, который не включает прототип, [... promotion elified].Если количество аргументов не равно числу параметров, поведение не определено. » –

3
int f(); 

В C это объявляет функцию, которая принимает переменное количество аргументов, т.е.это эквивалентно тому, что в C++

int f(...); 

Для проверки используйте следующие вместо int f();

int f(void); 

Это заставит компилятор жаловаться.

Обратите внимание: здесь также задействован приёмник C-linker ... C-компоновщик не проверяет аргументы, передаваемые функции в точке вызова, и просто ссылается на первый общедоступный символ с тем же именем. Таким образом, использование f() в основном разрешено из-за объявления int f(). Но компоновщик связывает функцию f (int, int, int) в течение времени ссылки на сайтах вызова. Надеюсь, что имеет какой-то смысл (пожалуйста, дайте мне знать, если он не делает)

+6

Нет, 'int f();' не объявляет функцию, которая принимает переменное количество аргументов. Он объявляет функцию, которая принимает * fixed *, но неуказанное количество аргументов. – caf

+1

фиксированный, но неуказанный переменный в моем словаре ... YMMV –

+0

int f (void) дает предупреждение ... но не ошибка .. без прототипа f получает вызов .. – kedar

4

Эта декларация:

int f(); 

... говорит компилятору "f является функция, которая принимает некоторое фиксированное число аргументов, и возвращается int ". Затем вы пытаетесь вызвать его одним, двумя и тремя аргументами - компиляторы C концептуально однопроходные (после предварительной обработки), поэтому на данный момент у компилятора нет информации, доступной для спора с вами.

Фактическая реализация f() принимает три int аргументов, так что вызовы, которые только обеспечивают один и два аргумент вызов неопределенного поведения - это ошибка, которая означает, что компилятор не обязан давать вам сообщение об ошибке, и ничего может случиться, когда вы запустите программу.

+0

в перспективе вопроса, мы все же можем сказать, что программа __works__ и не содержит ошибки (ошибка и неопределенное поведение относятся к разному домену «ошибка») – ShinTakezou

+3

Нет программы «undefined behavior» _ever_, даже если неопределенное поведение выведите правильные результаты :-) – paxdiablo

+0

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

1

Он работает отлично, так как int f() означает, что другой ответ уже сказал: это означает неопределенное количество аргументов. Это означает, что вы можете вызвать его с количеством аргументов, которые вы хотите (также более 3), без компилятора, говорящего об этом.

Причина, по которой она работает «под обложкой», заключается в том, что аргументы помещаются в стек, а затем обращаются к «из» стека в функции f. Если вы передадите 0 аргументов, функция i, j, k функции «соответствует» значениям в стеке, которые из функции PoV являются мусором. Тем не менее вы можете получить доступ к их значениям. Если вы передаете один аргумент, один из трех i j k получает доступ к значению, другие получают мусор. И так далее.

Обратите внимание, что те же рассуждения работают, если аргументы переданы каким-либо другим способом, но в любом случае это соглашение используется. Другим важным аспектом этих соглашений является то, что вызываемый не несет ответственности за корректировку стека; это зависит от вызывающего, который знает, сколько аргументов выдвинуто для реального. Если бы это было не так, определение f могло бы предполагать, что оно должно «настроить» стек для «выпуска» трех целых чисел, и это может вызвать какой-то крах.

Что вы написали штраф за текущий стандарт (на GCC компилируется без предупреждений, даже с -std=c99 -pedantic, есть предупреждение, но это о пропавшем int перед f определения), хотя многие люди находит отвратительно и называть это «устаревшей особенностью». Наверняка, ваше использование в примере кода не показывает никакой пользы, и, вероятно, это может помочь переборщикам ошибок более связующего использования прототипов!(Но все-таки, я предпочитаю C Ад)

добавить

Более «полезное» использование «функция», что не приводит к срабатыванию «неопределенное поведение» вопроса, может быть

#include<stdio.h> 
int f(); 

int main() 
{ 

    f(1); 
    f(2,2); 
    f(3,2,3); 
} 

int f(int i,int j,int k) 
{ 
    if (i == 1) printf("%d\n", i); 
    if (i == 2) printf("%d %d\n", i, j); 
    if (i == 3) printf("%d %d %d\n", i, j, k); 
} 
+2

Я принимаю серьезную проблему, говоря, что то, что написал OP, «отлично». Это может быть довольно легко сбой - например, в соответствии с соглашением о вызове «настраивать-стеки», например «stdcall» (о чем вы говорите в своем ответе). Было бы замечательно, если бы вызовы 'f (1);' и 'f (1, 2);' были опущены. – caf

+0

увы, как и в случае с соглашением о вызове с помощью pascal (я не буду говорить никогда, но в C они, вероятно, почти никогда не используются). Смотрите другие комментарии, где я поставил некоторый код, чтобы люди могли сосредоточиться на своем вопросе, а не на том факте, что он быстро написал код, который приводит к «неопределенному поведению», но в любом случае показал точку реального вопроса – ShinTakezou

+0

@ кафе, чтобы сделать мои слова более ясными; недавно я написал код для кодового гольфа.Я почти не говорю, что это хороший C, но это не главное для кода-гольфа, поэтому не стоит сосредотачиваться на нем: от кода-гольфа PoV, это «хороший» код. Чтобы показать вопрос о пользователе , код является точным и работает (т. е. не компилируется ошибка времени или сбой), как уже написано в комментариях, 'как f (1) и f (1,2) ссылки на f (int, int, int)' part делает мне кажется, что он ошибся (btw для C++ std, он дает ошибку времени компиляции и, вероятно, это более логично для OP) должен возникнуть – ShinTakezou

0

при компиляции ту же программу с помощью компилятора г ++ вы видите следующие ошибки -

g++ program.c 
program.c: In function `int main()': 
program.c:2: error: too many arguments to function `int f()' 
program.c:6: error: at this point in file 
program.c:2: error: too many arguments to function `int f()' 
program.c:7: error: at this point in file 
program.c:2: error: too many arguments to function `int f()' 
program.c:8: error: at this point in file 
program.c: At global scope: 
program.c:12: error: ISO C++ forbids declaration of `f' with no type 

Использование GCC с опцией -std = C99 просто выдает предупреждение

Компиляция ту же программу с тем же стандартом, который г ++ оказывает по умолчанию, выдает следующее сообщение:

gcc program.c -std=c++98 
cc1: warning: command line option "-std=c++98" is valid for C++/ObjC++ but not for C 

Мой ответ тогда бы быть или компиляторы соответствовать другой стандарт, который не является столь же ограничительными тот, который соответствует C++.

0

В C декларация должна объявлять, по крайней мере, тип возврата. Так

int f(); 

объявляет функцию, которая возвращает тип int. Это заявление не содержит никакой информации о параметрах, которые выполняет функция. Определение функции

f(int i,int j,int k) 
{ 

    printf("%d %d %d",i,j,k); 
} 

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

Чтобы избежать этих ошибок, вы должны использовать соответствующие прототипы функций, таких как

f(int,int,int);   //in your case 
f(void);     //if you have no parameters 
Смежные вопросы