Why does gcc allow arguments to be passed to a fun

2019-01-08 13:57发布

问题:

I don't get why does this code compile?

#include <stdio.h>
void foo() {
    printf("Hello\n");
}

int main() {
    const char *str = "bar";
    foo(str);
    return 0;
}

gcc doesn't even throw a warning that I am passing too many arguments to foo(). Is this expected behavior?

回答1:

In C, a function declared with an empty parameter list accepts an arbitrary number of arguments when being called, which are subject to the usual arithmetic promotions. It is the responsibility of the caller to ensure that the arguments supplied are appropriate for the definition of the function.

To declare a function taking zero arguments, you need to write void foo(void);.

This is for historic reasons; originally, C functions didn't have prototypes, as C evolved from B, a typeless language. When prototypes were added, the original typeless declarations were left in the language for backwards compatibility.

To get gcc to warn about empty parameter lists, use -Wstrict-prototypes:

Warn if a function is declared or defined without specifying the argument types. (An old-style function definition is permitted without a warning if preceded by a declaration which specifies the argument types.)



回答2:

For legacy reasons, declaring a function with () for a parameter list essentially means “figure out the parameters when the function is called”. To specify that a function has no parameters, use (void).

Edit: I feel like I am racking up reputation in this problem for being old. Just so you kids know what programming used to be like, here is my first program. (Not C; it shows you what we had to work with before that.)



回答3:

void foo() {
    printf("Hello\n");
}

foo(str);

in C, this code does not violates a constraint (it would if it was defined in its prototype-form with void foo(void) {/*...*/}) and as there is no constraint violation, the compiler is not required to issue a diagnostic.

But this program has undefined behavior according to the following C rules:

From:

(C99, 6.9.1p7) "If the declarator includes a parameter type list, the list also specifies the types of all the parameters; such a declarator also serves as a function prototype for later calls to the same function in the same translation unit. If the declarator includes an identifier list,142) the types of the parameters shall be declared in a following declaration list."

the foo function does not provide a prototype.

From:

(C99, 6.5.2.2p6) "If the expression that denotes the called function has a type that does not include a prototype [...] If the number of arguments does not equal the number of parameters, the behavior is undefined."

the foo(str) function call is undefined behavior.

C does not mandate the implementation to issue a diagnostic for a program that invokes undefined behavior but your program is still an erroneous program.



回答4:

Both the C99 Standard (6.7.5.3) and the C11 Standard (6.7.6.3) state:

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.

Since the declaration of foo is part of a definition, the declaration specifies that foo takes 0 arguments, so the call foo(str) is at least morally wrong. But as described below, there are different degrees of "wrong" in C, and compilers may differ in how they deal with certain kinds of "wrong".

To take a slightly simpler example, consider the following program:

int f() { return 9; }
int main() {
  return f(1);
}

If I compile the above using Clang:

tmp$ cc tmp3.c
tmp3.c:4:13: warning: too many arguments in call to 'f'
  return f(1);
         ~  ^
1 warning generated.

If I compile with gcc 4.8 I don't get any errors or warnings, even with -Wall. A previous answer suggested using -Wstrict-prototypes, which correctly reports that the definition of f is not in prototype form, but this is really not the point. The C Standard(s) allow a function definition in a non-prototype form such as the one above and the Standards clearly state that this definition specifies that the function takes 0 arguments.

Now there is a constraint (C11 Sec. 6.5.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.

However, this constraint does not apply in this case, since the type of the function does not include a prototype. But here is a subsequent statement in the semantics section (not a "constraint"), that does apply:

If the expression that denotes the called function has a type that does not include a prototype ... If the number of arguments does not equal the number of parameters, the behavior is undefined.

Hence the function call does result in undefined behavior (i.e., the program is not "strictly conforming"). However, the Standard only requires an implementation to report a diagnostic message when an actual constraint is violated, and in this case, there is no violation of a constraint. Hence gcc is not required to report an error or warning in order to be a "conforming implementation".

So I think the answer to the question, why does gcc allow it?, is that gcc is not required to report anything, since this is not a constraint violation. Moreover gcc does not claim to report every kind of undefined behavior, even with -Wall or -Wpedantic. It is undefined behavior, which means the implementation can choose how to deal with it, and gcc has chosen to compile it without warnings (and apparently it just ignores the argument).