Вы можете попросить включить проверку аргументов функций в языке С. Для этого достаточно объявить функцию с заданными типами аргументов (точно так же, как в языке С++). Такое объявление называется прототипом функции (function prototype). Тем не менее следует избегать объявлений, не задающих аргументы; они не являются прототипами функций и не включают механизм проверки типов.
int g(double); /* прототип — как в языке С ++ */
int h; /* не прототип — типы аргументов не указаны */
void my_fct
{
g; /*
ошибка: пропущен аргумент */
g("asdf"); /* ошибка: неправильный тип аргумента */
g(2); /* OK: 2 преобразуется в 2.0 */
g(2,3); /* ошибка: один аргумент лишний */
h; /* Компилятор допускает! Результат непредсказуем */
h("asdf"); /* Компилятор допускает! Результат непредсказуем */
h(2); /* Компилятор допускает! Результат непредсказуем */
h(2,3); /* Компилятор допускает! Результат непредсказуем */
}
В объявлении функции
h
не указан тип аргумента. Это не означает, что функция
h
не получает ни одного аргумента; это значит: принимает любой набор аргументов и надеется, что это набор при вызове окажется правильным. И снова отметим, что хороший компилятор предупредит об этой проблеме, а программа
lint
перехватит ее.
Существует специальный набор правил, регламентирующих преобразование аргументов, если в области видимости нет прототипа функции. Например, переменные типов
char
и
short
преобразуются в переменные типа
int
, а переменные типа
float
— в переменные типа
double
. Если вы хотите знать, скажем, что произойдет с переменной типа
long
, загляните в хороший учебник по языку С. Наша рекомендация проста: не вызывайте функций, не имеющих прототипов.
Обратите внимание на то, что, хотя компилятор допускает передачу аргументов неправильного типа, например параметр типа
char*
вместо параметра типа int, использование таких аргументов приводит к ошибкам. Как сказал Деннис Ритчи: “С — это язык программирования со строгим контролем типов и слабой проверкой”.
27.2.3. Определения функций
Можете определять функции точно так же, как в языке С++. Эти определения являются прототипами функций.
double square(double d)
{
return d*d;
}
void ff
{
double x = square(2); /* OK: переводим 2 в 2.0 и вызываем */
double y = square; /* пропущен аргумент */
double y = square("Hello"); /* ошибка: неправильный тип
аргументов */
double y = square(2,3); /*
ошибка: слишком много аргументов */
}
Определение функции без аргументов не является прототипом функции.
void f { /* что-то делает */ }
void g
{
f(2); /* OK в языке C; ошибка в языке C++ */
}
Код
void f; /* не указан тип аргумента */
означающий, что функция
f
может принять любое количество аргументов любого типа, выглядит действительно странно. В ответ на это я изобрел новое обозначение, в котором понятие “ничего” указывалось явным образом с помощью ключевого слова
void
(void — слово из четырех букв, означающее “ничего”).
void f(void); /* не принимает никаких аргументов */
Впрочем, вскоре я об этом пожалел, потому что эта конструкция выглядит странно и при последовательной проверке типов аргументов является излишней. Что еще хуже, Деннис Ритчи (автор языка C) и Дуг Мак-Илрой (Doug McIlroy) (законодатель мод в Исследовательском центре компьютерных наук в компании Bell Labs (Bell Labs Computer Science Research Center; см. раздел 22.2.5) назвали это решение “отвратительным”. К сожалению, оно стало очень популярным среди программистов, работающих на языке С. Тем не менее не используйте его в программах на языке С++, в которых оно выглядит не только уродливо, но и является совершенно излишним.
В языке C есть альтернативное определение функции в стиле языка Algol-60, в котором типы параметров (не обязательно) указываются отдельно от их имен.
int old_style(p,b,x) char* p; char b;
{
/* ... */
}
Это определение “в старом стиле” предвосхищает конструкции языка С++ и не является прототипом. По умолчанию аргумент без объявленного типа считается аргументов типа
int
. Итак, параметр
x
является аргументом функции
old_style
, имеющим тип
int
. Мы можем вызвать функцию
old_style
следующим образом:
old_style; /* OK: пропущены все аргументы */
old_style("hello",'a',17); /* OK: все аргументы имеют правильный тип */
old_style(12,13,14); /* OK: 12 — неправильный тип */
/* но old_style может не использовать p */
Компилятор должен пропустить эти вызовы (но мы надеемся, что он предупредит о первом и третьем аргументах).
Мы рекомендуем придерживаться следующих правил проверки типов аргументов функций.