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);
n
和c
是参数。 42
和ch
是论据。
引用的摘录仅涉及函数的参数,但没有提及关于函数的原型或参数的任何内容。
声明 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