Питання, яке ви задаєте, - це насправді два питання, а не одне. Більшість відповідей до цього часу намагалися охопити цілу річ загальною ковдрою "це стиль K&R", хоча насправді лише невелика частина цього має щось спільне з тим, що називається стилем K&R (якщо ви не бачите всю мову C як "K & R-style" в тій чи іншій мірі :)
Перша частина - це дивний синтаксис, який використовується у визначенні функції
int func(p, p2)
void *p;
int p2;
{
return 0;
}
Це насправді визначення функції у стилі K&R. Інші відповіді висвітлювали це досить добре. І насправді тут не так багато. Синтаксис застарілий, але все ще повністю підтримується навіть у C99 (за винятком правила "без неявного int" у C99, що означає, що в C99 ви не можете пропустити декларацію p2
).
Друга частина має мало спільного із стилем K&R. Я маю на увазі той факт, що функцію можна викликати за допомогою "обмінених" аргументів, тобто у такому виклику не відбувається перевірка типу параметра. Це має дуже мало спільного з визначенням стилю K&R як таке, але воно має все спільне з вашою функцією, яка не має прототипу. Розумієте, в C, коли ви оголошуєте таку функцію
int foo();
він фактично оголошує функцію, foo
яка приймає невизначену кількість параметрів невідомого типу . Ви можете назвати це як
foo(2, 3);
і як
j = foo(p, -3, "hello world");
і так далі (ви зрозуміли ідею);
Тільки дзвінок із належними аргументами буде "спрацьовувати" (це означає, що інші виробляють невизначену поведінку), але лише ви самі повинні забезпечити його правильність. Компілятор не повинен діагностувати неправильні, навіть якщо він якимось магічним чином знає правильні типи параметрів та їх загальну кількість.
Насправді така поведінка є особливістю мови 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();
тобто не оголошуються ні типи, ні загальна кількість параметрів. У своєму оригінальному дописі ви говорите "... схоже, вказується, скільки параметрів він використовує ...". Формально кажучи, ні! Після вашого двопараметричного func
визначення стилю K & R ви все ще можете телефонувати 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 вимагає, щоб функція була оголошена перед першим використанням, але декларація не повинна бути прототипом. Плутанина, очевидно, походить від популярного термінологічного змішування: багато людей називають будь-яку декларацію функції "прототипом", тоді як насправді "декларація функції" - це не те саме, що "прототип".