func()vs func(void)在c99中

void func()实际上,一个空的参数表示接受任何参数。

void func(void)接受参数。

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

6.7.5.3函数声明符(包括原型)
14标识符列表仅声明该函数参数的标识符。 函数声明器中的空列表是该函数定义的一部分,指定该函数没有参数。 函数声明器中的空列表不是该函数定义的一部分,它指定不提供有关参数数量或types的信息。

根据标准, func()func(void)是一样的吗?

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 

然而这两个调用在严格符合程序中都是非法的,因为它们根据6.5.2.2p6是明确的未定义行为。

而且,空括号被认为是过时的特征:

具有空括号的函数声明符(不是原型格式参数types声明符)的使用是过时的function。

具有单独的参数标识符和声明列表(不是原型格式参数types和标识符声明符)的函数定义的使用是过时的特征。

详细

有两个相关但却截然不同的概念:参数和参数。

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

  • 参数是函数中的名称/variables,它们被设置为input函数时参数的值

在以下摘录中:

 int foo(int n, char c) { ... } ... foo(42, ch); 

nc是参数。 42ch是论据。

引用的摘录仅涉及函数的参数,但没有提及关于函数的原型或参数的任何内容。


声明 void func1()意味着函数func1可以被任意数量的参数调用 ,也就是说,没有关于参数个数的信息被指定(作为一个单独的声明,C99指定它为“没有参数指定的函数),而声明void func2(void)意味着函数func2根本不接受任何参数

你的问题中的引用意味着在函数定义中void func1()void func2(void)都表示没有参数 ,即input函数时设置为参数值的variables名称void func() {}void func(); 前者声明func确实没有参数,而后者是一个函数func的声明, 没有指定参数也没有指定它们的types(没有原型的声明)。

但是,它们在定义上有所不同

  • 定义void func1() {}不会声明原型,而void func2(void) {} 会这样做 ,因为()不是参数types列表,而(void)是参数types列表( 6.7.5.3.10 ):

    作为列表中唯一项目的voidtypes的未命名参数的特例指定该函数没有参数。

    还有6.9.1.7

    如果声明符包含参数types列表,则列表还指定所有参数的types; 这样的一个声明器也可以作为一个函数原型,用于稍后在相同的翻译单元中调用相同的函数。 如果声明符包含一个标识符列表,则参数的types应在下面的声明列表中声明。 无论哪种情况,每个参数的types都按6.7.5.3中的描述调整参数types列表。 结果types应该是一个对象types。

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

  • void func1() { ... }仍然可以用任意数量的参数调用,而使用任何参数(6.5.2.2)调用void func2(void) { ... }是一个编译时错误:

    如果表示被调用函数的expression式有一个包含原型的types,则参数个数应与参数个数一致。 每个参数都应该有一个types,使得它的值可以被分配给一个对象,而这个对象的相应参数的types是不合格的。

    (重点是我的)

    这是一个约束 ,根据标准,一个符合的实现必须至less显示一个关于这个问题的诊断信息。 但是由于func1没有原型,所以不需要一致的实现来产生任何诊断。


但是,如果参数个数不等于参数个数,则行为是未定义的 6.5.2.2p6 :

如果表示被调用的函数的expression式的types不包含原型 ,[…] 如果参数的数量不等于参数的数量,则行为是不确定的。

所以在理论上,符合C99的编译器在这种情况下也被允许出现错误或诊断警告。 使用StoryTeller提供的证据表明, 铿锵可能诊断这一点 ; 然而,我的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 )函数的参数,而是指定原型函数( test2 )的任何参数的编译时错误,以及符合的实现必须诊断这是违反约束条件。

报价的重要部分以下面以粗体突出显示:

6.7.5.3函数声明符(包括原型)14一个标识符列表只声明函数参数的标识符。 函数声明器中的空列表是该函数定义一部分,指定该函数没有参数。 函数声明器中的空列表不是该函数定义一部分,它指定不提供有关参数数目或types的信息。

所以,当参数列表为空时,它们是一样的。 但这仅仅是一个函数的声明。

 void function1(); // No information about arguments void function2(void); // Function with zero arguments void function3() { // Zero arguments } void function4(void) { // Zero arguments } 

根据标准,func()和func(void)是一样的吗?

编号func(void)表示函数根本不需要任何参数; 而func()表示这个函数需要一个未指定数量的参数。 两者都是有效的,但func()风格已经过时,不应该使用。

这是来自预标准C的伪像。C99将其标记为过时。

6.11.6函数声明符 :

具有空括号的函数声明符(不是原型格式参数types声明符)的使用是过时的function。

截至C11,它仍然是过时的,并没有从标准中删除。

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

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

函数定义中的声明符指定了被定义函数的名称和参数的标识符。 如果声明包含参数types列表 ,则列表还指定所有参数的types; 这样的一个声明器也可以作为一个函数原型,用于稍后在相同的翻译单元中调用相同的函数。

问题是:

根据标准, func()func(void)是一样的吗?

不, void func()void func(void)之间的本质区别在于它们的调用。

C11§6.5.2.2/ 2 函数调用 (在约束部分内):

如果表示被调用函数的expression式的types包含原型 ,则自variables的数量应与参数的数量一致 。 每个参数都应该有一个types,使得它的值可以被分配给一个对象,而这个对象的相应参数的types是不合格的。

注意参数≠参数。 该函数可能不包含参数,但可能有多个参数。

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

然而,从技术angular度来看,至less有一个参数是不确定的 (参见Antti Haapala的评论 )。

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

如果参数个数不等于参数个数,则行为是不确定的。

因此,这个差别是微妙的:

  • 当用void定义一个函数时,当参数个数与参数(及其types)不匹配时,由于违反约束(第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; } 

请注意,在这种情况下优化编译器可能会中断参数。 例如,这是Clang如何根据SysV ABI调用约定在x86-64上编译上述代码(不包括func1的调用)和-01

 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