Требуются ли прототипы для всех функций в C89, C90 или C99?

Чтобы быть действительно совместимыми со стандартами, должны ли все функции в C (кроме main) иметь прототип, даже если они используются только после их определения в одной и той же единице перевода?

6 ответов

Решение

Это зависит от того, что вы подразумеваете под "действительно соответствием стандартам". Тем не менее, короткий ответ: "Хорошая идея - убедиться, что все функции имеют прототип в области видимости перед использованием".

Более квалифицированный ответ отмечает, что если функция принимает переменные аргументы (особенно printf() семейством функций), то прототип должен быть в рамках, чтобы быть строго совместимым со стандартами. Это верно для C89 (из ANSI) и C90 (из ISO; то же самое, что и C89 за исключением нумерации разделов). Однако, кроме функций 'varargs', функции, которые возвращают int не должны быть объявлены, и функции, которые возвращают что-то кроме int действительно нужно объявление, которое показывает тип возвращаемого значения, но не нужен прототип для списка аргументов.

Обратите внимание, однако, что если функция принимает аргументы, которые подлежат "нормальному продвижению" в отсутствие прототипов (например, функция, которая принимает char или же short - оба из которых преобразуются в int; более серьезно, возможно, функция, которая принимает float вместо double), тогда нужен прототип. Стандарт был слабым в этом отношении, чтобы позволить старому коду на C компилироваться под стандартные совместимые компиляторы; старый код не был написан для того, чтобы беспокоиться о том, чтобы функции были объявлены перед использованием - и по определению, старый код не использовал прототипы, так как они не стали доступны в C, пока не появился стандарт.

C99 запрещает "неявный int"... это означает, что оба странных случая похожи на "static a;' (int по умолчанию), а также неявные объявления функций. Они упоминаются (наряду с примерно 50 другими важными изменениями) в предисловии к ИСО / МЭК 9899:1999, в котором этот стандарт сравнивается с предыдущими версиями:

  • удалить неявное int
    ...
  • удалить неявное объявление функции

В ISO/IEC 9899:1990, §6.3.2.2 вызовов функций указано:

Если выражение, которое предшествует списку аргументов в скобках в вызове функции, состоит исключительно из идентификатора, и если для этого идентификатора не видно никакого объявления, идентификатор неявно объявляется точно так, как если бы в самом внутреннем блоке, содержащем вызов функции, объявление:

extern int identifier();

появился.38

38 То есть идентификатор с областью действия блока, объявленный как имеющий внешнюю связь с функцией типа без информации о параметрах и возвращающий int, Если на самом деле он не определен как имеющий функцию "функция, возвращающая int"Поведение не определено.

Этот пункт отсутствует в стандарте 1999 года. Я (пока) не отслеживал изменение словоблудия, которое позволяет static a; в C90 и запрещает это (требуется static int a;) в C99.

Обратите внимание, что если функция является статической, она может быть определена до того, как ее использовать, и ей не нужно предшествовать объявление. GCC можно убедить, что нестатическая функция определена без объявления перед ней (-Wmissing-prototypes).

Прототип - это объявление функции, которое определяет типы параметров функции.

Pre-ANSI C (язык, описанный в первом издании Kernighan & Ritchie "The C Programming Language" 1978 года) не имел прототипов; для объявления функции было невозможно описать количество или типы параметров. Вызывающий абонент должен был передать правильное количество и тип аргументов.

ANSI C представил "прототипы", объявления, которые определяют типы параметров (функция, заимствованная из раннего C++).

Начиная с C89/C90 (стандарты ANSI и ISO описывают один и тот же язык), законно вызывать функцию без видимого объявления; неявное объявление предоставляется. Если неявное объявление несовместимо с фактическим определением (скажем, вызов sqrt("foo")тогда поведение не определено. Ни это неявное объявление, ни объявление, не являющееся прототипом, не могут быть совместимы с функцией с переменными значениями, поэтому любой вызов функции с переменными параметрами (например, printf или же scanf) должен иметь видимый прототип.

C99 отбросил неявные объявления. Любой вызов функции без видимого объявления является нарушением ограничения, требующего диагностики компилятора. Но это объявление все еще не обязательно должно быть прототипом; это может быть объявление старого стиля, в котором не указываются типы параметров.

С11 не внес существенных изменений в этой области.

Таким образом, даже в соответствии со стандартом ISO C 2011 года объявления и определения функций старого стиля (которые "устарели" с 1989 года) все еще разрешены в соответствующем коде.

Для всех версий C, начиная с 1989 года, из соображений стиля очень мало причин не использовать прототипы для всех функций. Объявления и определения старого стиля хранятся только во избежание взлома старого кода.

Нет, функциям не всегда нужен прототип. Единственное требование состоит в том, чтобы функция была "объявлена" перед ее использованием. Есть два способа объявить функцию: написать прототип или написать саму функцию (называемую "определением"). Определение всегда является объявлением, но не все объявления являются определениями.

Хороший совет при написании новых функций - писать их вверх ногами, а main внизу, поэтому, когда вы передумаете над аргументами функции или типом возврата, вам не нужно будет также исправлять прототип. Постоянное исправление прототипов и обработка всех предупреждений компилятора, когда они устарели, становится действительно утомительным.

Как только ваши функции будут работать гладко, переместите код в хорошо названный модуль и поместите прототипы в файл.h с тем же именем. Это экономит серьезное время. Самая большая помощь в продуктивности, которую я нашел за 5 лет.

Да, каждая функция должна иметь прототип, но этот прототип может появиться либо в отдельном объявлении, либо как часть определения функции. Определения функций, написанные на C89 и выше, естественно, имеют прототипы, но если вы пишете вещи в классическом стиле K&R, то:

main (argc, argv)

  int argc;
  char **argv;

{
  ...
}

тогда определение функции не имеет прототипа. Если вы пишете стиль ANSI C (C89), то:

main (int argc, char **argv) { ... }

тогда у определения функции есть прототип.

Насколько мне известно (в ANSI C89/ISO C90), нет. Я не уверен насчет C99; Впрочем, я бы ожидал того же.

Личное примечание: я пишу прототипы функций только когда...

  1. Мне нужно (когда A() вызывает B() и B() вызывает A()), или
  2. Я экспортирую функцию; в противном случае это кажется излишним.
Другие вопросы по тегам