当前位置:  开发笔记 > 编程语言 > 正文

c99中的func()vs func(void)

如何解决《c99中的func()vsfunc(void)》经验,为你挑选了4个好方法。

void func() 实际上,空参数表示接受任何参数.

void func(void) 不接受任何争论.

但在标准C99中,我找到了这样的界限:

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

根据标准,func()并且func(void)是相同的?



1> Antti Haapal..:

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它们是明确未定义的行为  .

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

使用带有空括号的函数声明符(不是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)的任何参数; 任何符合要求的实现都必须诊断它,因为它是一个约束违规.


如果`int foo(){...};`被称为`foo(5);`,那么标准对实现可能会做什么没有任何要求,但是一些实现可以并且确实将这些调用定义为具有有用的行为,特别是如果函数的代码使用内联汇编或其他实现定义的扩展.关于这样的调用,因为约束违反将阻止这种实现提供有用的特征.

2> Mats..:

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

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
}


这个答案是对的.它们是相同的参数,但是`()`1没有指定原型,因此函数3没有原型 - 它也没有任何*参数*但是参数的数量或它们的类型,没有检查.
@usr - 引用的段落说它确实意味着在定义中,而不是声明.你不能在这方面与标准争论.
@usr是什么意思?
@KerrekSB我觉得我在重复自己.但我会再试一次:如果没有事先声明,那么该定义就是声明.如果该定义没有参数,那么它"需要未指定(但不是无限)的参数".(顺便说一句,如果某事定义为int fun(){}它**显然*没有参数 - 我可以看到因为我不是盲目的.但这并不反驳我所说的.也许,你可以解释我之间的区别"参数"和"参数").
@StoryTeller如果*definition*没有参数,那么它有..参数;-)我不确定引用的部分是否与问题直接相关.即使是定义为`int func(){..}`的函数(没有原型)在这种情况下仍然可以接受参数,该定义也可以作为*declration*.
这意味着它需要一个未指定数量的参数.
@usr - 它不能.查看clang所说的链接.
@KerrekSB现在,重新阅读你的评论`... function3()中的空列表"指定该函数没有参数"; 因此,不能用任何零参数来调用它来找出废话.

3> usr..:

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

func(void)表示该功能将不会在所有的参数; 而且func()说该函数需要一个未指定数量的参数.两者都有效,但func()风格已过时,不应使用.

这是来自预标准C的工件.C99将其标记为过时.

6.11.6函数声明符:

使用带有空括号的函数声明符(不是prototype-format参数类型声明符)是一个过时的功能.

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



4> Grzegorz Szp..:

函数定义中的空参数列表表示它不包含原型,也没有任何参数.

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到调用该功能.

例:

#include 

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

推荐阅读
和谐啄木鸟
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有