Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does mismatched prototype and definition with empty argument list give different results in GCC and Clang?

Given (simplified) code snippet:

void foo(int a, int b); // declaration with prototype

int main(void)
{
    foo(1, 5); // type-checked call (i.e. because of previous prototype)
    return 0;
}

void foo() // old-style definition (with empty argument list)
{

}

and command-line options (though, as I checked they are not important):

-x c -std=c11 -pedantic -Wall

gcc 7.2 fails to compile it with following error message:

error: number of arguments doesn't match prototype

while clang 4.0 translates it without any complaints.

Which implementation is correct according to C Standard? Is it valid that old-style definition "cancels" previous prototype?

like image 522
Grzegorz Szpetkowski Avatar asked Aug 29 '17 18:08

Grzegorz Szpetkowski


2 Answers

(C11, 6.7p4 Constraints) "All declarations in the same scope that refer to the same object or function shall specify compatible types"

and

(C11, 6.7.6.3p14) "An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. [...]"

My opinion is constraint of 6.7p4 is violated and diagnostic has to be issued.

EDIT:

as pointed out by @hvd it is actually not correct. 6.7.6.3p14 does not mean void foo() {} provides a prototype for foo as per DR#317. In that sense, the 6.7p4 constraint is not violated and so clang is right not to complain.

like image 87
ouah Avatar answered Sep 23 '22 11:09

ouah


Disclaimer: I am not a language-lawyer, but I play one on stackoverflow.

If the compiler does not issue a diagnostic, it would be non-conforming, and could be considered a bug if the compiler claims to be conforming.

C.2011§6.7.6.3¶14 (emphasis mine):

An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.

Thus, the definition of foo specifies no parameters, while the declaration of foo earlier specified two parameters.

C.2011§6.7.6.3¶15:

For two function types to be compatible, both shall specify compatible return types.146) Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types.
146) If both function types are ‘‘old style’’, parameter types are not compared.

Thus, the two declarators of foo are not compatible.
Dang! From @hvd's comment:

It's well-established that void foo() does not provide a prototype, even in a definition. There was a DR that answered this explicitly. The type of foo in that definition is void foo(), not void foo(void), and void foo() and void foo(int, int) are compatible types. This answer is incorrect.

The emphasized part of the text above from the standard is the loophole that allows for the disagreement in number of arguments, but compatible types. Although the function definition specifies a function that takes no arguments, since the parameter type list is actually missing, there is actually no incompatibility between the type of foo in its function prototype and the type of foo in the function definition.

Thus, clang 4.0 seems to have it right, since there is no constraint violation.

My original argument becomes invalid, so editing out that part of my original answer.


In comments you actually presented the following example:

void foo () {}

int main () { foo(1, 2); return 0; }

And asked why the compiler does not complain for this case. This is actually addressed here, but in a nutshell: C.2011 still accepts K&R C function definition syntax. So, while void foo() {} is a definition that takes no arguments, the prototype that is used for argument validation is the same as void foo();, because the empty argument list is parsed as K&R. The modern C syntax to force proper argument checking would be to use void foo(void) {} instead.

like image 43
jxh Avatar answered Sep 21 '22 11:09

jxh