Альтернативный (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 текст ссылки

Другие вопросы по тегам