Альтернативный (K&R) C-синтаксис для объявления функций по сравнению с прототипами
Что полезного в этом C
синтаксис - используя объявления в стиле 'K&R'?
int func (p, p2)
void *p;
int p2;
{
return 0;
}
Я смог написать это в Visual Studios 2010beta
//yes the params are flipped
void f()
{
void *v=0;
func(5,v);
}
Я не понимаю Какой смысл этого синтаксиса? Я могу написать:
int func (p, p2)
int p2;
{
return 0;
}
//and write
int func (p, p2)
{
return 0;
}
Единственное, что он указывает, это то, сколько параметров он использует и тип возвращаемого значения. Я думаю, параметры без типов это круто, но зачем это нужно и int paranName
после объявления функции? Это странно.
И это все еще стандартный C?
7 ответов
Вопрос, который вы задаете, - это на самом деле два вопроса, а не один. Большинство ответов до сих пор пытались покрыть все это общим ответом "это стиль 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 требует, чтобы функция была объявлена перед первым использованием, но объявление не обязательно должно быть прототипом. Эта путаница, по-видимому, связана с популярной терминологической путаницей: многие люди называют любое объявление функции "прототипом", тогда как на самом деле "объявление функции" - это не то же самое, что "прототип".
Это довольно старый синтаксис K&R C (предшествующий ANSI/ISO C). В настоящее время вы больше не должны его использовать (поскольку вы уже заметили его главный недостаток: компилятор не будет проверять типы аргументов за вас). Тип аргумента на самом деле по умолчанию int
в вашем примере.
В то время этот синтаксис использовался, иногда можно было бы найти такие функции, как
foo(p, q)
{
return q + p;
}
который был на самом деле действительным определением, как типы для foo
, p
, а также q
по умолчанию int
,
Это просто старый синтаксис, который предшествует синтаксису "ANSI C", с которым вы, возможно, более знакомы. Обычно это называется " K & R C".
Компиляторы поддерживают его, чтобы он был завершенным и, конечно, мог работать со старыми кодами.
Это пережиток того времени, когда у C не было прототипов для функций. Еще тогда (я думаю) функции должны были вернуться int
и все его аргументы предполагались int
, Проверка параметров функции не проводилась.
Вам гораздо лучше использовать прототипы функций на текущем языке Си.И вы должны использовать их в C99 (C89 по-прежнему принимает старый синтаксис).
И C99 требует, чтобы функции были объявлены (возможно, без прототипа). Если вы пишете новую функцию с нуля, вам нужно предоставить объявление... сделайте ее также прототипом: вы ничего не потеряете и получите дополнительную проверку от компилятора.
Это оригинальный синтаксис K&R до стандартизации C в 1989 году. C89 представил прототипы функций, заимствованные из C++, и устарел синтаксис K&R. Нет причин использовать его (и множество причин этого не делать) в новом коде.
К сожалению, Ричи умер, поэтому мы больше не можем его спросить. Однако изучение документированной истории C дает некоторые действительно важные подсказки, которые ответят на ваш первоначальный вопрос. Этот ответ представляет собой вывод, основанный на доказательствах.
C уходит корнями в язык B, который, в свою очередь, представляет собой упрощенную синтаксическую версию BCPL. BCPL — это язык, который работает только с целыми числами ; то есть единственный тип данных, доступный вам на этом языке, — это ровно столько битов, сколько составляет одно машинное слово. На 16-битной машине BCPL позволяет манипулировать 16-битными значениями; на 32-битной машине, 32-битные значения и т. д.
Соответственно, когда вы определяете функцию в BCPL, у нее не будет никаких аннотаций типов:
LET qux(a,b,c) = VALOF $(
/* ...etc... */
RESULTIS someValueHere;
$)
Обратите внимание, что $( и $) теперь можно заменить на { и }, но когда BCPL был наиболее популярен (середина 60-х — середина 70-х годов), не все терминальные клавиатуры имели символы { и }.
Томпсон упростил синтаксис BCPL для создания B, чтобы соответствовать ограничениям ресурсов PDP-7, которые он использовал в то время. Ключевые слова LET и VALOF были удалены, поскольку они ничего не давали ни программисту, ни компилятору, а разделители блоков были заменены фигурными скобками, что упростило лексер. Подробность RESULTIS была заменена на «return», и теперь мы получаем:
qux(a,b,c) {
/* ...etc... */
return someValueHere;
}
Это похоже на действительный код C, но с таким же успехом это может быть и код B. Помните, что B, как и BCPL, был однотипным языком.
Поддержка нескольких типов появилась только в C, когда накладные расходы на обработку байтов, встроенных в машинные слова, стали более значительными. Если мне позволено рассуждать здесь еще больше, Ричи (который работал с Томпсоном над созданием B), скорее всего, просто повторно использовал код синтаксического анализатора B и просто настроил его для поддержки разных типов. Таким образом, вполне естественно, что синтаксис C в то время выглядел следующим образом:
qux(a,b,c)
char a;
float b;
{
/* ...etc... */
return someVal;
}
Этот синтаксис позволил бы более или менее легко переносить большой объем кода B в экосистему C того времени.
C всегда был гибким языком, особенно на заре своего развития. Знаете ли вы, что оригинальный метод статического объявления констант в языке C выглядел так?
someVal 1024;
Поговорим о минимальном синтаксисе, а? Сегодня этот синтаксис не поддерживается даже в большинстве компиляторов C в стиле «K&R».
Так в чем же ценность (для вас) этого синтаксиса? Ничего. Абсолютно ничего. Это всего лишь эволюционный артефакт, если хотите, случайность наследия C и его продолжающейся эволюции от BCPL.
Я надеюсь, что это поможет установить исторический контекст этого необычного синтаксиса.
Это более старый стиль объявлений функций. Пожалуйста, смотрите эту ссылку.
/questions/23854948/kakovyi-osnovnyie-razlichiya-mezhdu-ansi-c-i-k-and-r-c текст ссылки