C99 中的 func() 与 func(void)

func() vs func(void) in C99

void func()

实际上,空参数意味着接受任何参数。

void func(void) 不接受参数。

但在标准 C99 中,我发现这样的行:

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.

按照标准,func()func(void)是一样的吗?

引用的重要部分在下面以粗体突出显示:

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.

所以,当参数列表为空的函数及其主体时,它们是相同的。但它只是一个函数的声明。

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

void function3() {
    // Zero arguments
}

void function4(void) {
    // Zero arguments
}

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

没有。 func(void) 表示该函数根本不需要 没有 个参数;而 func() 表示该函数采用未指定数量的参数。两者都有效,但 func() 样式已过时,不应使用。

这是来自 pre-standard C 的工件。C99 将其标记为已过时。

6.11.6 Function declarators:

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

从 C11 开始,它仍然过时并且没有从标准中删除。

函数定义中的空参数列表意味着它不包含原型,也没有任何参数。

C11 §6.9.1/7 函数定义 (强调正在进行的报价是我的)

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.

问题问:

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

没有。 void func()void func(void)的本质区别在于调用。

C11 §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. 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.

注意参数≠参数。该函数可以不包含任何参数,但它可以有多个参数。

由于用空参数定义的函数不引入原型,因此不会根据调用对其进行检查,因此理论上它可以提供任意 个参数。

然而,它在技术上是一个 undefined behavior (UB) to call such function with at least one argument (see )。

C11 §6.5.2.2/6 函数调用(在语义部分):

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

因此,区别很微妙:

  • 当使用 void 定义函数时,如果参数数量与参数(及其类型)不匹配,则不会编译,因为违反约束(§6.5.2.2/2 ).这种情况需要来自符合标准的编译器的诊断消息。
  • 如果它是用空参数定义的,它可能可能不会编译(不需要来自符合标准的编译器),但是 调用 这样的函数是 UB。

示例:

#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;
}

请注意,optimizing compiler may cut off the arguments in such a case. For instance, this is how Clang 根据 SysV ABI 调用约定在 x86-64 上使用 -01 编译上述代码(不包括 func1 的调用):

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

TL;DR

在声明中,

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

行为完全不同。第一个声明一个没有任何原型的函数——它可以接受任意数量的参数!而后者声明了一个带有原型的函数,它没有参数也不接受任何参数。

定义中

void func1() { }     // obsolescent

void func2(void) { }
  • 前者声明并定义了一个函数func1,没有参数,没有原型

  • 后者声明并定义了一个函数func2,其原型没有参数

这两者的行为截然不同,因为 C 编译器 必须 在调用参数数量错误的原型函数时打印诊断消息,而 不需要 在调用没有原型的函数时这样做。

即,给定上述定义

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

但是 这两个 调用在 strictly-conforming 程序中都是非法的,因为根据 6.5.2.2p6.

它们是明确未定义的行为

此外,空括号被认为是过时的功能:

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

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

详细

有 2 个相关但不同的概念:参数和自变量。

  • 参数是传递给函数的值。

  • 参数是函数中的names/variables,在函数进入时设置为参数的值

以下摘录:

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

...

    foo(42, ch);

nc 是参数。 42ch 是参数。

引用的摘录仅涉及函数的参数,而没有提及函数的原型或参数。


声明 void func1() 意味着函数 func1 可以用 任意数量的 参数调用,即没有指定参数个数的信息(作为一个单独的声明,C99 将其指定为“没有参数指定的函数”),而声明 void func2(void) 表示函数func2 根本不接受任何 参数

你问题中的引用意味着在 函数定义中 void func1()void func2(void) 都表示没有 parameters,即 在输入函数时设置为 arguments 值的变量名称。 void func() {}void func(); 形成对比,前者声明 func 确实不带参数,而后者是函数 func 的声明,其中 参数指定了它们的类型(没有原型的声明)。

然而,它们的不同之处在于 definition-wise

  • 定义void func1() {}没有声明原型,而void func2(void) {}有,因为()不是参数类型列表,而(void)是参数类型列表(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.

    还有 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.

    func1 的函数定义声明符 包含 参数类型列表 ,因此该函数不包含没有原型。

  • void func1() { ... } 仍然可以使用任意数量的参数调用,而使用任何参数调用 void func2(void) { ... } 是 compile-time 错误 (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.

    (强调我的)

    这是一个约束,根据标准,符合标准的实现必须显示至少一条关于此问题的诊断消息.但是由于 func1 没有原型,因此不需要符合要求的实现来生成任何诊断。


但是,如果参数的数量不等于参数的数量,行为是未定义的 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.

因此在理论上,符合 C99 标准的编译器也可以在这种情况下出错或诊断警告。 provided evidence that clang might diagnose this;然而,我的 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 将指定任何参数视为 compile-time 错误原型函数的参数 (test2);任何符合规范的实现 必须 对此进行诊断,因为它违反了约束条件。