Вопрос, который вы задаете, на самом деле два вопроса, а не один. Большинство ответов до сих пор пытались покрыть все это общим ответом «это стиль K & R», хотя на самом деле только небольшая его часть имеет какое-либо отношение к тому, что известно как стиль 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-стилем. Я имею в виду тот факт, что функция может быть вызвана с «замененными» аргументами, то есть при таком вызове проверка типа параметра не выполняется. Это имеет очень мало общего с определением стиля K & R, но оно имеет отношение к вашей функции, не имеющей прототипа. Видите ли, в C, когда вы объявляете такую функцию
int foo();
фактически объявляет функцию foo
, которая принимает неопределенное количество параметров неизвестного типа . Вы можете назвать это как
foo(2, 3);
и как
j = foo(p, -3, "hello world");
и так далее (вы поняли);
Только вызов с правильными аргументами будет «работать» (это означает, что другие выдают неопределенное поведение), но только вы должны убедиться в его правильности. Компилятору не требуется диагностировать неправильные, даже если он каким-то волшебным образом знает правильные типы параметров и их общее количество.
На самом деле, это поведение функция языка Си. Опасный, но, тем не менее, особенность. Это позволяет вам сделать что-то вроде этого
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 func
вы все равно можете назвать func
как
func(1, 2, 3, 4, "Hi!");
и в нем не будет никакого нарушения ограничений. (Как правило, качественный компилятор выдаст вам предупреждение).
Кроме того, иногда упускается из виду тот факт, что
int f()
{
return 0;
}
также является определением функции в стиле K & R, в котором не представлен прототип. Чтобы сделать его «современным», вам нужно было бы указать явный void
в списке параметров
int f(void)
{
return 0;
}
Наконец, вопреки распространенному мнению, в C99 полностью поддерживаются как определения функций в стиле K & R, так и объявления не-прототипированных функций. Первый из них устарел с C89 / 90, если я правильно помню. C99 требует, чтобы функция была объявлена перед первым использованием, но объявление не обязательно должно быть прототипом. Эта путаница, очевидно, связана с популярной терминологической путаницей: многие люди называют любое объявление функции «прототипом», тогда как на самом деле «объявление функции» - это не то же самое, что «прототип».