C99 中的 func() 与 func(void)
Posted
技术标签:
【中文标题】C99 中的 func() 与 func(void)【英文标题】:func() vs func(void) in C99 【发布时间】:2017-06-07 19:41:27 【问题描述】:void func()
实际上,空参数意味着接受任何参数。
void func(void)
不接受任何参数。
但在标准 C99 中,我发现这样的行:
6.7.5.3 Function declarators (including prototypes) 14 标识符列表仅声明函数参数的标识符。 作为该函数定义一部分的函数声明器中的空列表指定该函数没有参数。 不属于该函数定义的函数声明器中的空列表指定该函数没有提供有关参数数量或类型的信息。
按照标准,func()
和func(void)
是一样的吗?
【问题讨论】:
我不知道标准,但实际上显然不是。 在我看来,标准说它们是不同的:没有提供的信息不同于不应该提供的参数 @Margaret,在粗体部分,如果声明符是定义的一部分,则表示没有参数 作为函数原型,void funct()
和 void func(void)
是不同的。但是当它们作为定义的一部分出现时,它们都是相同的。 void func() ...
和 void func(void) ...
都没有参数。
in c: func(void) vs. func()的可能重复
【参考方案1】:
引用的重要部分在下面以粗体突出显示:
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
【讨论】:
@usr 是什么意思? @usr - 引用的段落说它确实意味着在定义中,而不是在声明中。你不能与这方面的标准争论。 @StoryTeller 如果 definition 没有参数,那么它有..没有参数 ;-) 我不确定引用的部分是否与问题直接相关。即使定义为int func() ..
(没有原型)的函数在这种情况下仍然可以接受参数,该定义也充当declration。
@KerrekSB 我觉得我在重复自己。但我会再试一次:如果没有事先声明,则定义充当声明。如果该定义没有参数,那么它“采用未指定(但不是无限)数量的参数”。 (顺便说一句,如果某些东西被定义为 int fun() 它非常显然没有参数 - 我可以看到,因为我不是盲人。但这并不能反驳我所说的。也许,你可以解释一下“参数”和“参数”之间的区别)。
这个答案是错误的。它们在参数方面是相同的,但 ()
没有指定原型,因此函数 3 没有原型 - 它也没有任何 parameters 但参数的数量,或它们的类型,不检查。【参考方案2】:
按照标准,func()和func(void)是一样的吗?
没有。 func(void)
表示该函数根本不接受 no 参数;而func()
表示该函数采用未指定数量的参数。两者都有效,但 func()
样式已过时,不应使用。
这是来自准标准 C 的工件。C99 将其标记为已过时。
6.11.6 Function declarators:
使用带空括号的函数声明符(不是原型格式的参数类型声明符)已过时。
从 C11 开始,它仍然过时,并没有从标准中删除。
【讨论】:
希望它在 2x 内被删除。 但是根据6.7.5,看起来是一样的。 @2501:在将原型添加到 C 之前,大多数用于微型计算机的 C 编译器都使用调用约定,将参数放置在堆栈上可预测的位移处,并且编译器知道的参数之外的参数将是无害的。该标准不需要这样的语义,因为并非所有平台都支持它,但是因为在某些平台上它允许通过其他方式无法有效实现的语义,因此应该继续有一种标准认可的方式,通过该方式提供这样的平台语义可以继续这样做。那并不意味着... ...不提供此类语义的编译器将有义务接受该语法——仅仅是有 some 标准语法的实现可以支持或不支持闲来无事,以表明旧的语义。如果希望简化这种迁移的编译器编写者使用该语法来模拟旧的调用约定,那么拥有一种新语法实际上可以促进依赖于语义的代码向新系统的迁移。【参考方案3】:函数定义中的空参数列表意味着它不包含原型,也没有任何参数。
C11 §6.9.1/7 函数定义 (持续引用的重点是我的)
函数定义中的声明符指定函数的名称 定义的函数及其参数的标识符。 如果 声明符包含一个参数类型列表,该列表还指定了 所有参数的类型; 这样的声明符也可以作为 函数原型,用于以后在同一个函数中调用同一个函数 翻译单元。
问题问:
按照标准,
func()
和func(void)
是一样的吗?
没有。 void func()
和void func(void)
的本质区别在于它们的调用。
C11 §6.5.2.2/2 函数调用(在约束部分):
如果表示被调用函数的表达式有一个类型 包括一个原型,参数的数量应与 参数个数。每个参数都应该有一个类型,使得它的 可以将值分配给具有非限定版本的对象 对应参数的类型。
注意参数≠参数。函数可以不包含参数,但可以有多个参数。
由于使用空参数定义的函数不会引入原型,因此不会对其调用进行检查,因此理论上可以为它提供任何个参数。
但是,从技术上讲,使用至少一个参数调用此类函数是 undefined behavior (UB)(请参阅 Antti Haapala's comments)。
C11 §6.5.2.2/6 函数调用(在语义部分):
如果参数的数量不等于参数的数量, 行为未定义。
因此,差异是微妙的:
当使用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 可能会在这种情况下切断参数。例如,这就是 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
【讨论】:
附录 J.2。未定义的行为:“对于在范围内没有函数原型的函数的调用,参数的数量不等于参数的数量(6.5.2.2)。”,因此在严格符合的程序中是不允许的。【参考方案4】: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,它们是明确未定义的行为。
此外,空括号被认为是过时的功能:
使用带空括号的函数声明符(不是原型格式的参数类型声明符)已过时。
和
使用具有单独参数标识符和声明列表(不是原型格式的参数类型和标识符声明符)的函数定义已过时。
详细说明
有 2 个相关但不同的概念:参数和参数。
参数是传递给函数的值。
参数是函数中的名称/变量,在函数进入时设置为参数的值
摘录如下:
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
的声明,既不参数也不 em> 指定了它们的类型(没有原型的声明)。
然而,它们在定义上却有所不同
定义void func1()
没有声明原型,而void func2(void)
声明了原型,因为()
不是参数类型列表,而(void)
是参数类型列表(6.7.5.3.10):
类型为 void 的未命名参数作为列表中唯一项的特殊情况指定该函数没有参数。
还有6.9.1.7
如果声明符包含参数类型列表,则 list 还指定了所有参数的类型;这样的声明器还可以用作函数原型,以便以后调用同一翻译单元中的同一函数。如果声明器包含标识符列表,则参数的类型应在以下声明列表中声明。在任何一种情况下,每个参数的类型都会按照 6.7.5.3 中对参数类型列表的描述进行调整;结果类型应为对象类型。
func1
的函数定义声明符不包含参数类型列表,因此该函数没有原型。
void func1() ...
仍然可以使用任意数量的参数调用,而使用任何参数调用 void func2(void) ...
是编译时错误 (6.5.2.2):
如果表示被调用函数的表达式的类型包含原型,则参数的数量应与参数的数量一致。每个参数都应该有一个类型,这样它的值就可以被分配给一个具有其对应参数类型的非限定版本的对象。
(强调我的)
这是一个约束,根据标准规定,符合标准的实现必须至少显示一条关于此问题的诊断消息。但由于func1
没有原型,因此不需要符合要求的实现来生成任何诊断信息。
但是,如果参数的数量不等于参数的数量,则行为未定义 6.5.2.2p6:
如果表示被调用函数的表达式的类型不包含原型,[...] 如果参数的数量不等于参数的数量,则行为未定义。
因此,理论上,在这种情况下,符合 C99 的编译器也可以出错或诊断出警告。 StoryTeller 提供了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 将指定原型函数的任何参数视为编译时错误( test2
);任何符合要求的实现必须诊断这是因为它违反了约束。
【讨论】:
6.9.1.13 明确表示:“这两种定义之间的区别在于,第一种形式作为原型声明,强制转换后续调用函数的参数,而第二种形式则不是。” (这两个定义差不多是同一个函数声明器,有一个参数列表和一个标识符列表。一个空列表必须是一个标识符列表,等效的参数列表就是void
)
我没有发现 C 规范支持 函数定义 作为 void func1()
是一个过时的功能。也许您认为 6.11.6 函数声明符 适用于 函数定义?
嗯,您对 6.11.7 函数定义 的链接及其“单独的参数标识符和声明列表”不适用于void func1()
。这确实适用于void func3(a,b) int a; int b;
或者;函数定义中也有一个函数 declarator,所以 6.11.6 适用
标准没有对如果 int foo() ...;
被称为 foo(5);
的实现可能做什么强加任何要求,但是一些实现可以并且确实将此类调用定义为具有有用的行为,特别是如果代码因为该函数使用内联汇编或其他实现定义的扩展。将此类调用视为违反约束会阻止此类实现提供有用的功能。以上是关于C99 中的 func() 与 func(void)的主要内容,如果未能解决你的问题,请参考以下文章
预定义宏__func___Pragma变长参数宏定义以及__VA_ARGS__
如何将网格传递给“void my_func(void * args)”