func() vs func(void) in c99

2019-01-16 23:21发布

问题:

void func() In practice, an empty parameter means any argument is accepted.

void func(void) accepts no argument.

But in Standard C99, I find such lines:

6.7.5.3 Function declarators (including prototypes)
14 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.

according to the standard, func() and func(void) is the same?

回答1:

TL;DR

In declarations,

void func1();     // obsolescent
void func2(void);

the behaviour is quite different. The first one declares a function without any prototype - and it may take any number of arguments! Whereas the latter declares a function with a prototype, that has no parameters and accepts no arguments.

In definitions

void func1() { }     // obsolescent

and

void func2(void) { }
  • The former declares and defines a function func1 that has no parameters and no prototype

  • The latter declares and defines a function func2 with a prototype that has no parameters.

These two behave distinctly in that whereas the C compiler must print a diagnostic message when calling a prototyped function with wrong number of arguments, it needn't do so when calling a function without prototype.

I.e, given the definitions above

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

However both calls are illegal in strictly-conforming programs as they're explicitly undefined behaviour as per 6.5.2.2p6.

Furthermore, the empty parentheses are considered an obsolescent feature:

The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.

and

The use of function definitions with separate parameter identifier and declaration lists (not prototype-format parameter type and identifier declarators) is an obsolescent feature.

In detail

There are 2 related, yet distinct concepts: parameters and arguments.

  • arguments are the values passed into the function.

  • parameters are the names/variables within the function that are set to the values of the arguments when the function entered

In the following excerpt:

int foo(int n, char c) {
    ...
}

...

    foo(42, ch);

n and c are parameters. 42 and ch are arguments.

The quoted excerpt only concerns the parameters of a function, but doesn't mention anything about the prototype or arguments to the function.


The declaration void func1() means that the function func1 can be called with any number of arguments, i.e. no information about the number of arguments is specified (as a separate declaration, C99 specifies this as "function with no parameter specification), whereas the declaration void func2(void) means that the function func2 does not accept any arguments at all.

The quote in your question means that within a function definition, void func1() and void func2(void) both signal them that there are no parameters, i.e. variable names that are set to the values of the arguments when the function is entered. The void func() {} contrasts with void func(); the former declares that func indeed takes no parameters, whereas the latter is a declaration for a function func for which neither parameters nor their types are specified (a declaration without prototype).

However, they yet differ definition-wise in that

  • The definition void func1() {} doesn't declare a prototype, whereas void func2(void) {} does, because () is not a parameter type list, whereas (void) is a parameter type list (6.7.5.3.10):

    The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.

    and further 6.9.1.7

    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, the types of the parameters shall be declared in a following declaration list. In either case, the type of each parameter is adjusted as described in 6.7.5.3 for a parameter type list; the resulting type shall be an object type.

    The declarator of function definition for func1 does not contain a parameter type list, and thus the function then doesn't have a prototype.

  • void func1() { ... } can still be called with any number of arguments, whereas it is a compile-time error to call void func2(void) { ... } with any arguments (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. Each argument shall have a type such that its value may be assigned to an object with the unqualified version of the type of its corresponding parameter.

    (emphasis mine)

    This is a constraint, which according of the standard says that a conforming implementation must display at least one diagnostic message about this problem. But since func1 doesn't have a prototype, a conforming implementation is not required to produce any diagnostics.


However, if the number of arguments does not equal the number of parameters, the behaviour is undefined 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.

So in theory a conforming C99 compiler is also allowed to error or diagnose a warning in this case. StoryTeller provided evidence that clang might diagnose this; however, my GCC doesn't seem to do it (and this might also be required for it to be compatible with some old obscure code too):

void test() { }

void test2(void) { }

int main(void) {
    test(1, 2);
    test2(1, 2);
}

When the above program is compiled with gcc -std=c99 test.c -Wall -Werror, the output is:

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) { }
      ^~~~~

That is, the arguments are not checked at all against the parameters of a function whose declaration in definition is not prototyped (test) whereas GCC considers it as a compile-time error to specify any arguments to a prototyped function (test2); any conforming implementation must diagnose this as it is a constraint violation.



回答2:

The significant part of the quote is highlighted in bold below:

6.7.5.3 Function declarators (including prototypes) 14 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.

So, when the parameter list is empty for a function with its body, they are the same. But of it is just a declaration of a function.

void function1(); // No information about arguments
void function2(void); // Function with zero arguments

void function3() {
    // Zero arguments
}

void function4(void) {
    // Zero arguments
}


回答3:

according to the standard, func() and func(void) is the same?

No. func(void) says the function takes no arguments at all; whereas func() says the function takes an unspecified number of arguments. Both are valid but the func() style are obsolete and shouldn't be used.

This is an artifact from pre-standard C. C99 marked this as obsolete.

6.11.6 Function declarators:

The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.

As of C11, it still remains as obsolescent and hasn't been removed from the standard.



回答4:

The empty parameter list inside a function definition means that it does not include a prototype nor has any parameters.

C11 §6.9.1/7 Function definitions (emphasis in ongoing quotes is mine)

The declarator in a function definition specifies the name of the function being defined and the identifiers of its parameters. 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.

The question asks:

according to the standard, func() and func(void) is the same?

No. The essential difference between void func() and void func(void) lies in their calls.

C11 §6.5.2.2/2 Function calls (within constraints section):

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. Each argument shall have a type such that its value may be assigned to an object with the unqualified version of the type of its corresponding parameter.

Notice that parameters ≠ arguments. The function may contain no parameters, but it may have multiple arguments.

As function defined with empty parameters does not introduce a prototype, it's not checked against its calls, so in theory it may be supplied with whatever number of arguments.

However, it is technically an undefined behavior to call such function with at least one argument (see Antti Haapala's comments).

C11 §6.5.2.2/6 Function calls (within semantics section):

If the number of arguments does not equal the number of parameters, the behavior is undefined.

Hence, the difference is subtle:

  • When a function is defined with void, it won't compile when number of arguments don't match with parameters (along with their types), because of constaint violation (§6.5.2.2/2). Such situation requires diagnostic message from conforming compiler.
  • If it is defined with empty parameters, it may or may not compile (there is no requirement for diagnostic message from conforming compiler), however it's UB to call such function.

Example:

#include <stdio.h>

void func1(void) { puts("foo"); }
void func2()     { puts("foo"); }

int main(void)
{
    func1(1, 2); // constraint violation, it shouldn't compile
    func2(3, 4); // may or may not compile, UB when called
    return 0;
}

Note that optimizing compiler may cut off the arguments in such case. For instance, this is how Clang compiles the above code (excluding func1's call) with -01 on x86-64 according to the SysV ABI calling conventions:

main:                                   # @main
        push    rax          ; align stack to the 16-byte boundary
        call    func2        ; call func2 (no arguments given)
        xor     eax, eax     ; set zero as return value
        pop     rcx          ; restore previous stack position (RSP)
        ret


标签: c c99