There are (source):
void f(); // declaration (1)
void f(void); // declaration with prototype (2)
void f() { ... } // definition (3)
void f(void) { ... } // definition with prototype (4)
What is the difference between 3 and 4? The source doesn't explain that difference and to me 4 looks redundant.
The functions created by (3) and (4) are identical—both have no parameters, and the compiler may generate identical code for them. However, in C 2018 (still the standard at this moment) the type information attached to the function name differs, and this can affect calls to the function. C 2024 is expected to change this, making (3) equivalent to (4).
Assuming there is no other visible declaration of f
that modifies the declaration, then calls to the f
declared in (4) are subject to this constraint in C 2018 6.5.2.2 2:
If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters…
The C standard requires a constraint violation to be diagnosed; if a program contains a constraint violation, a compiler must produce a diagnostic message. In contrast, calls to the f
declared in (3) are not subject to that constraint, and a compiler is not required to produce a diagnostic message (although it may do so).
Additionally, after (3), the compiler analyzes a call to f
using the rules in C 2018 6.5.2.2 6:
If the expression that denotes the called function has a type that does not include a prototype,…
After (4), the compiler analyzes a call to f
using 6.5.2.2 7:
If the expression that denotes the called function has a type that does include a prototype,…
However, there are generally no consequences of this because these paragraphs specify only two things:
When the expression denoting the called function does not have a prototype, the behavior is undefined if the number of arguments does not equal the number of parameters.
How the arguments are converted in preparation for the call.
If there are no arguments, then 2. does not apply. If there are arguments, then the behavior is undefined in both cases, and I do not see any opportunity for a different conversion of the arguments to cause any meaningful difference in behavior.
Interestingly, although the standard does not require a compiler to diagnose when the function in (3) is called with a parameter, it does require a compiler to diagnose when it is redeclared with a parameter, such as void f(int x);
. C 6.7 2 has this constraint, requiring diagnosis:
All declarations in the same scope that refer to the same object or function shall specify compatible types.
and the specification for compatible function types in 6.7.6.3 15 says:
… If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters,…
Since void f(int x)
has a parameter type list, and void f() {}
contains an empty identifier list, they must, to be compatible, agree in the number of parameters. They do not agree, so they are not compatible, so the constraint violation must be diagnosed.
It seems like this is an oversight in the standard; since this constraint compels the compiler to retain knowledge of the number of parameters in a function definition, even if it uses an identifier list and not a prototype, the standard could also have required compilers to diagnose calls with an incorrect number of parameters without requiring the compiler to retain any additional information about the function.
However, that would be useful only in situations where the function definition were visible at the point of the call, which is often not the case, as the function may be defined in another translation unit or later in the same unit. So perhaps not an important case to cover. Nonetheless, it could have caught some bugs.