void func()
实际上,空参数表示接受任何参数.
void func(void)
不接受任何争论.
但在标准C99中,我找到了这样的界限:
6.7.5.3函数声明
符(包括原型) 14标识符列表仅声明函数参数的标识符.函数声明符中的空列表是该函数定义的一部分,指定该函数没有参数.函数声明符中的空列表不是该函数定义的一部分,它指定不提供有关参数数量或类型的信息.
根据标准,func()
并且func(void)
是相同的?
在声明中,
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它们是明确未定义的行为 .
此外,空括号被认为是一个过时的功能:
使用带有空括号的函数声明符(不是prototype-format参数类型声明符)是一个过时的功能.
和
使用具有单独的参数标识符和声明列表(而不是原型格式参数类型和标识符声明符)的函数定义是一个过时的功能.
有两个相关但不同的概念:参数和参数.
arguments是传递给函数的值.
参数是函数中的名称/变量,它们在函数输入时设置为参数的值
在以下摘录中:
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)
都表示没有参数,即在输入函数时设置为参数值的变量名称.void func() {}
与void func();
前者的对比声明func
确实没有参数,而后者是一个函数的声明func
,既没有指定参数也没有指定它们的类型(没有原型的声明).
但是,他们在定义方面却有所不同
该定义void func1() {}
不会声明原型,而是void func2(void) {}
因为()
它不是参数类型列表,而是(void)
参数类型列表(6.7.5.3.10):
void类型的未命名参数作为列表中唯一项的特殊情况指定该函数没有参数.
进一步6.9.1.7
如果声明符包含参数类型列表,则列表还指定所有参数的类型; 这样的声明符也可以作为函数原型,以便以后调用同一个翻译单元中的相同函数.如果声明符包含标识符列表,则参数的类型应在以下声明列表中声明.在任何一种情况下,每个参数的类型都按照6.7.5.3中的描述调整参数类型列表; 结果类型应为对象类型.
函数定义的声明func1
符不包含参数类型列表,因此函数没有原型.
void func1() { ... }
仍然可以使用任意数量的参数调用,而void func2(void) { ... }
使用任何参数调用它是一个编译时错误(6.5.2.2):
如果表示被调用函数的表达式具有包含原型的类型,则参数的数量应与参数的数量一致.每个参数都应具有一个类型,使得其值可以分配给具有其相应参数类型的非限定版本的对象.
(强调我的)
这是一个约束,根据标准说,符合要求的实现必须至少显示一条关于此问题的诊断消息.但由于func1
没有原型,因此不需要符合要求的实现来生成任何诊断.
但是,如果参数的数量不等于参数的数量,则行为是未定义的 6.5.2.2p6:
如果表示被调用函数的表达式具有不包含原型的类型,则[...] 如果参数数量不等于参数数量,则行为未定义.
因此理论上,在这种情况下,也允许符合标准的C99编译器错误或诊断警告.StoryTeller提供证据表明clang可能会诊断出这一点 ; 但是,我的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) { } ^~~~~
也就是说,根本不对函数的参数检查参数,该函数的定义中的声明不是prototyped(test
),而GCC认为它是编译时错误,用于指定原型函数(test2
)的任何参数; 任何符合要求的实现都必须诊断它,因为它是一个约束违规.
下面以粗体突出显示引用的重要部分:
6.7.5.3函数声明符(包括原型)14标识符列表仅声明函数参数的标识符.函数声明符中的空列表是该函数定义的一部分,指定该函数没有参数.函数声明符中的空列表不是该函数定义的一部分,它指定不提供有关参数数量或类型的信息.
因此,当参数列表对于具有其主体的函数为空时,它们是相同的.但它只是一个函数的声明.
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函数声明符:
使用带有空括号的函数声明符(不是prototype-format参数类型声明符)是一个过时的功能.
截至C11,它仍然是过时的,并没有从标准中删除.
函数定义中的空参数列表表示它不包含原型,也没有任何参数.
C11§6.9.1/ 7 函数定义 (正在引用的重点是我的)
函数定义中的声明符指定要定义的函数的名称及其参数的标识符.如果声明符包含参数类型列表,则列表还指定所有参数的类型; 这样的声明符也可以作为函数原型,以便以后调用同一个翻译单元中的相同函数.
问题是:
根据标准,
func()
并且func(void)
是相同的?
号之间的本质区别void func()
,并void func(void)
就在于他们的呼声.
C11§6.5.2.2/ 2 函数调用(在约束部分内):
如果表示被调用函数的表达式具有包含原型的类型 ,则参数的数量应与参数的数量一致.每个参数都应具有一个类型,使得其值可以分配给具有其相应参数类型的非限定版本的对象.
请注意,参数≠参数.该函数可能不包含任何参数,但它可能有多个参数.
由于使用空参数定义的函数不会引入原型,因此不会根据其调用进行检查,因此理论上可以提供任意数量的参数.
但是,从技术上讲,使用至少一个参数调用此类函数是一种未定义的行为(请参阅Antti Haapala的注释).
C11§6.5.2.2/ 6 函数调用(在语义部分):
如果参数数量不等于参数数量,则行为未定义.
因此,差异是微妙的:
当定义函数void
时,由于constaint违规(§6.5.2.2/ 2),当参数数量与参数(及其类型)不匹配时,它将不会编译.这种情况需要来自符合编译器的诊断消息.
如果是用空的参数所定义,其可以或可以不进行编译(存在用于从符合编译器诊断消息不要求),但是它的UB到调用该功能.
例:
#includevoid 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