Требуются ли прототипы для всех функций в 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; Впрочем, я бы ожидал того же.
Личное примечание: я пишу прототипы функций только когда...
- Мне нужно (когда A() вызывает B() и B() вызывает A()), или
- Я экспортирую функцию; в противном случае это кажется излишним.