Ответ 1
Больше терминологии (C, а не С++): прототип функции объявляет типы своих аргументов. В противном случае функция не имеет прототипа.
void f(); // Declaration, but not a prototype
void f(void); // Declaration and prototype
void f(int a, int b, float c); // Declaration and prototype
Объявления, которые не являются прототипами, являются задержками с pre-ANSI C, начиная с дней K & R C. Единственная причина использования объявления старого стиля - поддерживать двоичную совместимость со старым кодом. Например, в Gtk 2 есть объявление функции без прототипа - оно есть случайно, но оно не может быть удалено без нарушения двоичных файлов. Стандартные комментарии C99:
6.11.6 Объявление функций
Использование деклараторов функций с пустыми круглыми скобками (а не параметром прототипа тип деклараторы) является устаревшей особенностью.
Рекомендация: Я предлагаю скомпилировать весь код C в GCC/Clang с помощью -Wstrict-prototypes
и -Wmissing-prototypes
, в дополнение к обычному -Wall -Wextra
.
Что произойдет
void f(); // declaration
void f(int a, int b, float c) { } // ERROR
Объявление не согласуется с телом функции! Это на самом деле ошибка времени компиляции, и это потому, что вы не можете иметь аргумент float
в функции без прототипа. Причина, по которой вы не можете использовать float
в функции unprototyped, состоит в том, что, когда вы вызываете такую функцию, все аргументы получают повышение с помощью определенных рекламных акций по умолчанию. Здесь фиксированный пример:
void f();
void g()
{
char a;
int b;
float c;
f(a, b, c);
}
В этой программе a
повышается до int
1 а c
- до double
. Поэтому определение f()
должно быть:
void f(int a, int b, double c)
{
...
}
См. C99 6.7.6, пункт 15,
Если один тип имеет список типов параметров, а другой тип указан декларатор функции, который не является частью определения функции и содержит пустой список идентификаторов, список параметров не должен иметь терминатор с многоточием и тип каждого параметр должен быть совместим с типом, который возникает в результате применения объявления по умолчанию.
Ответ 1
Что происходит во время компиляции в случаях 1 и 2, когда мы вызываем
f
с правильными аргументами, неправильными аргументами и без аргументов вообще? Что происходит во время выполнения?
Когда вы вызываете f()
, параметры получают повышение с помощью рекламных акций по умолчанию. Если продвинутые типы соответствуют фактическим типам параметров для f()
, тогда все это хорошо. Если они не совпадают, вероятно, они будут скомпилированы, но вы обязательно получите поведение undefined.
"Undefined поведение" является spec-speak для "мы не делаем никаких гарантий относительно того, что произойдет". Возможно, ваша программа потерпит крах, возможно, она будет работать нормально, возможно, она пригласит ваших родственников на ужин.
Есть два способа получить диагностику во время компиляции. Если у вас есть сложный компилятор с возможностями кросс-модульного статического анализа, вы, вероятно, получите сообщение об ошибке. Вы также можете получать сообщения для не-прототипированных деклараций функций с помощью GCC, используя -Wstrict-prototypes
- который я рекомендую включить во всех ваших проектах (кроме файлов, которые используют Gtk 2).
Ответ 2
Если я объявляю
f
аргументами, но определяю их без них, будет ли это иметь значение? Должен ли я обращаться к аргументам из тела функции?
Он не должен компилироваться.
Исключения
На самом деле есть два случая, когда аргументам функции разрешено не соглашаться с определением функции.
-
Допустим
char *
передать функцию, ожидающуюvoid *
, и наоборот. -
Допустим, что целочисленный тип со знаком подписан в функцию, ожидающую неподписанную версию этого типа, или наоборот, если значение представлено в обоих типах (т.е. оно не является отрицательным, и не вне диапазона подписанного типа).
Сноски
1: возможно, что char
продвигается до unsigned int
, но это очень необычно.