Ответ 1
TL; DR
В объявлениях
void func1(); // obsolescent
void func2(void);
поведение совсем другое. Первый объявляет функцию без какого-либо прототипа - и может потребоваться любое количество аргументов! В то время как последний объявляет функцию с прототипом, которая не имеет параметров и не принимает аргументов.
В определениях
void func1() { } // obsolescent
и
void func2(void) { }
-
Первая объявляет и определяет функцию
func1
, у которой нет параметров и нет прототипа -
Последний объявляет и определяет функцию
func2
с прототипом, который не имеет параметров.
Эти два ведут себя отчетливо, в то время как компилятор C должен печатать диагностическое сообщение при вызове прототипированной функции с неправильным количеством аргументов, не нужно делать это при вызове функции без прототипа.
I.e, учитывая приведенные выше определения
func1(1, 2, 3); // need not produce a diagnostic message
func2(1, 2, 3); // must always produce a diagnostic message
// as it is a constraint violation
Однако оба вызова являются незаконными в строго согласованных программах, поскольку они явно undefined по сравнению с 6.5.2.2p6.
Кроме того, пустые круглые скобки считаются устаревшей особенностью:
Использование деклараторов функций с пустыми скобками (не деклараторы типов параметров прототипа) является устаревшей функцией.
и
Использование определений функций с отдельными идентификаторами параметров и списками деклараций (а не тип и тип идентификатора прототипа) является устаревшей функцией.
Подробнее
Существует 2 взаимосвязанных, но разных понятия: параметры и аргументы.
-
аргументы - это значения, переданные в функцию.
-
параметры - это имена/переменные внутри функции, которые заданы значениями аргументов при вводе функции
В следующем отрывке:
int foo(int n, char c) {
...
}
...
foo(42, ch);
n
и c
являются параметрами. 42
и ch
являются аргументами.
Приведенная выдержка относится только к параметрам функции, но ничего не говорит о прототипе или аргументах функции.
Объявление void func1()
означает, что функция func1
может быть вызвана с любым числом аргументов, то есть информация о количестве аргументов не указана (в качестве отдельного объявления C99 указывает это как "функция без спецификации параметров", тогда как объявление void func2(void)
означает, что функция func2
вообще не принимает никаких аргументов.
Цитата в вашем вопросе означает, что в определении функции, void func1()
и void func2(void)
оба сигнализируют им, что нет параметров, то есть имена переменных, которые установлены на значения аргументов когда функция введена. void func() {}
контрастирует с void func();
, первый из них заявляет, что func
действительно не принимает никаких параметров, тогда как последний является объявлением для функции func
, для которой не указаны ни параметры, ни их типы (объявление без прототипа).
Однако они все же отличаются по определению в том, что
-
Определение
void func1() {}
не объявляет прототип, тогда какvoid func2(void) {}
делает, потому что()
не является списком типов параметров, тогда как(void)
является списком типов параметров (6.7.5.3.10):Частный случай неименованного параметра типа void как единственного элемента в списке указывает, что функция не имеет параметров.
и далее 6.9.1.7
Если декларатор содержит список типов параметров, list также определяет типы всех параметров; такой декларатор также служит прототипом функции для последующих вызовов одной и той же функции в одной и той же системе перевода. Если декларатор содержит список идентификаторов, типы параметров должны быть объявлены в следующем списке объявлений. В любом случае тип каждого параметра настраивается, как описано в 6.7.5.3 для списка типов параметров; результирующий тип должен быть типом объекта.
Объявление определения функции для
func1
не содержит список типов параметров, и поэтому функция не имеет прототипа. -
void func1() { ... }
все равно можно вызывать с любым количеством аргументов, тогда как ошибка времени компиляции вызываетvoid func2(void) { ... }
с любыми аргументами (6.5.2.2):Если выражение, которое обозначает вызываемую функцию, имеет тип , который включает прототип, количество аргументов должно совпадать с количеством параметров. Каждый аргумент должен иметь такой тип, чтобы его значение могло быть присвоено объекту с неквалифицированной версией типа его соответствующего параметра.
(акцент мой)
Это ограничение, которое согласно стандарту говорит о том, что соответствующая реализация должна отображать хотя бы одно диагностическое сообщение об этой проблеме. Но так как
func1
не имеет прототипа, соответствующая реализация не требуется для какой-либо диагностики.
Однако, если количество аргументов не равно числу параметров, поведение undefined 6.5.2.2p6:
Если выражение, которое обозначает вызываемую функцию, имеет тип, который не включает прототип, [...] Если количество аргументов не равно числу параметров, поведение undefined.
Таким образом, теоретически совместимый компилятор C99 также допускает ошибку или диагностику предупреждения в этом случае. Использование StoryTeller дает доказательства того, что clang может диагностировать это; однако мой GCC, похоже, не делает этого (и для этого также может потребоваться совместимость с каким-то старым неясным кодом):
void test() { }
void test2(void) { }
int main(void) {
test(1, 2);
test2(1, 2);
}
Когда вышеприведенная программа скомпилирована с помощью gcc -std=c99 test.c -Wall -Werror
, вывод:
test.c: In function ‘main’:
test.c:7:5: error: too many arguments to function ‘test2’
test2(1, 2);
^~~~~
test.c:3:6: note: declared here
void test2(void) { }
^~~~~
То есть аргументы вообще не проверяются параметрами функции, декларация которых в определении не прототипирована (test
), тогда как GCC считает ее ошибкой компиляции, чтобы указать любые аргументы прототипированной функции ( test2
); любая соответствующая реализация должна диагностировать это, поскольку это нарушение ограничения.