Ответ 1
Вопрос, который вы задаете, - это действительно два вопроса, а не один. Большинство ответов до сих пор пытались охватить всю вещь с помощью общего одеяла "это ответ K & R style", в то время как на самом деле лишь малая его часть имеет какое-либо отношение к тому, что известно как стиль K & R (если вы не видите весь язык C как "K & R-style" так или иначе:)
Первая часть - это странный синтаксис, используемый в определении функции
int func(p, p2)
void *p;
int p2; /* <- optional in C89/90, but not in C99 */
{
return 0;
}
Это на самом деле определение функции K & R-стиля. Другой ответ на это довольно хорошо. И на самом деле этого мало. Синтаксис устарел, но полностью поддерживается даже в C99 (за исключением правила "no implicit int" в C99, что означает, что на C99 вы не можете опустить объявление p2
).
Вторая часть имеет мало общего с K & R-style. Я обращаюсь к тому факту, что функция может вызываться с "замененными" аргументами, т.е. В таком вызове не происходит проверки типа параметра. Это очень мало связано с определением K & R-style per se, но оно имеет все, что связано с тем, что ваша функция не имеет прототипа. Вы видите, в C, когда вы объявляете такую функцию
int foo();
он фактически объявляет функцию foo
, которая принимает неопределенное количество параметров неизвестного типа. Вы можете назвать его
foo(2, 3);
и как
j = foo(p, -3, "hello world");
ans и так далее (вы получаете идею);
Только вызов с правильными аргументами будет "работать" (что означает, что другие производят поведение undefined), но вы полностью уверены в его правильности. Компилятор не обязан диагностировать неправильные, даже если он каким-то образом знает правильные типы параметров и их общее число.
Собственно, это поведение является особенностью языка C. Однако опасный, но особенный. Это позволяет вам делать что-то вроде этого
void foo(int i);
void bar(char *a, double b);
void baz(void);
int main()
{
void (*fn[])() = { foo, bar, baz };
fn[0](5);
fn[1]("abc", 1.0);
fn[2]();
}
то есть. смешивать разные типы функций в "полиморфном" массиве без каких-либо типов (типы вариационной функции здесь не могут быть использованы). Опять же, присущие опасности этой техники совершенно очевидны (я не помню, чтобы когда-либо ее использовали, но я могу представить, где это может быть полезно), но это C в конце концов.
Наконец, бит, который связывает вторую часть ответа с первым. Когда вы выполняете определение функции K & R-стиля, оно не вводит прототип функции. Что касается типа функции, ваше определение func
объявляет func
как
int func();
то есть. ни типы, ни общее количество параметров не объявлены. В своем оригинальном посте вы говорите: "... кажется, это то, сколько параметров он использует...". Формально это не так! После вашего двухпараметрического определения K & R-style func
вы все равно можете вызвать func
как
func(1, 2, 3, 4, "Hi!");
и в нем не будет никаких ограничений. (Как правило, качественный компилятор даст вам предупреждение).
Кроме того, иногда упускается из виду тот факт, что
int f()
{
return 0;
}
также является определением функции K & R-стиля, которое не вводит прототип. Чтобы сделать его "современным", вам нужно будет поместить явный void
в список параметров
int f(void)
{
return 0;
}
Наконец, вопреки распространенному мнению, как определения функций K & R-стиля, так и не-прототипированные объявления функций полностью поддерживаются в C99. Первый был устаревшим с C89/90, если я правильно помню. C99 требует, чтобы функция была объявлена до первого использования, но декларация не требуется для прототипа. Путаница, по-видимому, проистекает из популярного терминологического смешения: многие люди называют любое объявление функции "прототипом", в то время как на самом деле "объявление функции" - это не то же самое, что "prototype".