首页 文章

使用C void参数“void foo(void)”或不使用“void foo()”更好吗? [重复]

提问于
浏览
190

这个问题在这里已有答案:

什么是更好的: void foo()void foo(void) ?随着虚空它看起来丑陋和不一致,但我被告知这是好的 . 这是真的?

编辑:我知道一些旧的编译器做了奇怪的事情,但如果我只使用GCC,是 void foo() 好吗?那么 foo(bar); 会被接受吗?

6 回答

  • 222
    void foo(void);
    

    这是在C中说“无参数”的正确方法,它也适用于C语言 .

    但:

    void foo();
    

    在C和C中意味着不同的东西!在C中它表示"could take any number of parameters of unknown types",在C中它表示与 foo(void) 相同 .

    变量参数列表函数本质上是非类型安全的,应尽可能避免 .

  • 8

    在C中指定参数有两种方法 . 一种是使用标识符列表,另一种是使用参数类型列表 . 标识符列表可以省略,但类型列表不能 . 因此,假设一个函数在函数定义中不带参数,则使用(省略的)标识符列表执行此操作

    void f() {
        /* do something ... */
    }
    

    这有一个参数类型列表:

    void f(void) {
        /* do something ... */
    }
    

    如果在参数类型列表中,只有一个参数类型为void(它必须没有名称),那么这意味着该函数不带参数 . 但是这两种定义函数的方式在它们声明的内容上有所不同 .

    标识符列表

    第一个定义该函数采用特定数量的参数,但既不传递计数也不传递所需的类型 - 就像使用标识符列表的所有函数声明一样 . 因此呼叫者必须事先知道类型和计数 . 因此,如果调用者调用函数给它一些参数,则行为是未定义的 . 例如,堆栈可能会损坏,因为被调用的函数在获得控制权时需要不同的布局 .

    不推荐在函数参数中使用标识符列表 . 它在过去使用过,并且仍然存在于许多 生产环境 代码中 . 由于这些参数提升,它们可能会导致严重的危险(如果提升的参数类型与函数定义的参数类型不匹配,行为也未定义!)并且当然不那么安全 . 所以总是在没有参数的函数中使用 void thingy,仅用于声明和函数定义 .

    参数类型列表

    第二个定义函数接受零参数并且还传达 - 就像使用参数类型列表声明函数的所有情况一样,它被称为 prototype . 如果调用者调用该函数并给它一些参数,那就是一个错误,编译器会发出一个适当的错误 .

    声明函数的第二种方法有很多好处 . 当然之一是检查参数的数量和类型 . 另一个区别是,因为编译器知道参数类型,所以它可以将参数的隐式转换应用于参数类型 . 如果不存在参数类型列表,则无法完成,并且参数将转换为提升类型(称为默认参数提升) . 例如, char 将变为 int ,而 float 将变为 double .

    函数的复合类型

    顺便说一下,如果文件包含省略的标识符列表和参数类型列表,则参数类型列表“wins” . 最后的函数类型包含一个原型:

    void f();
    void f(int a) {
        printf("%d", a);
    }
    
    // f has now a prototype.
    

    那是因为两个声明都没有说出任何矛盾的说法 . 然而,第二个问题还有待说明 . 这是一个被接受的论点 . 反过来也可以这样做

    void f(a) 
      int a;
    { 
        printf("%d", a);
    }
    
    void f(int);
    

    第一个使用标识符列表定义函数,而第二个使用包含参数类型列表的声明为其提供原型 .

  • 1

    void foo(void) 更好,因为它明确地说:不允许参数 .

    void foo() 意味着你可以(在一些编译器下)发送参数,至少如果这是你的函数的声明而不是它的定义 .

  • 3

    在C中, main()main(void) 之间存在 no 差异 .

    But 在C中,将使用任意数量的参数调用 main() .

    例:

    main ( ){
        main(10,"abc",12.28);
        //Works fine !
        //It won't give the error. The code will compile successfully.
        //(May cause Segmentation fault when run)
    }
    

    main(void) 将在没有任何参数的情况下调用 . 如果我们尝试通过,那么最终导致编译器错误 .

    例:

    main (void) {
         main(10,"abc",12.13);
         //This throws "error: too many arguments to function ‘main’ "
    }
    
  • 18

    C99 quotes

    这个答案旨在引用和解释C99 N1256 standard draft的相关部分 .

    Definition of declarator

    声明者这个词会出现很多,所以让我们理解它 .

    从语言语法中,我们发现以下下划线字符是声明符:

    int f(int x, int y);
        ^^^^^^^^^^^^^^^
    
    int f(int x, int y) { return x + y; }
        ^^^^^^^^^^^^^^^
    
    int f();
        ^^^
    
    int f(x, y) int x; int y; { return x + y; }
        ^^^^^^^
    

    声明符是函数声明和定义的一部分 .

    有两种类型的声明符:

    • 参数类型列表

    • 标识符列表

    Parameter type list

    声明如下:

    int f(int x, int y);
    

    定义如下:

    int f(int x, int y) { return x + y; }
    

    它被称为参数类型列表,因为我们必须给出每个参数的类型 .

    Identifier list

    定义如下:

    int f(x, y)
        int x;
        int y;
    { return x + y; }
    

    声明如下:

    int g();
    

    我们不能声明具有非空标识符列表的函数:

    int g(x, y);
    

    因为6.7.5.3 "Function declarators (including prototypes)"说:

    3函数声明符中不属于该函数定义的标识符列表应为空 .

    它被称为标识符列表,因为我们只在 f(x, y) 上给出标识符 xy ,类型后来 .

    这是一种较旧的方法,不应再使用 . 6.11.6函数声明符说:

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

    引言解释了什么是过时功能:

    某些功能已过时,这意味着它们可能会在本国际标准的未来版本中考虑撤销 . 由于它们的广泛使用,它们被保留,但是它们在新实现(用于实现特性)或新程序(用于语言[6.11]或库特征[7.26])中的使用不鼓励使用

    f() vs f(void) for declarations

    当你写的时候:

    void f();
    

    它必然是一个标识符列表声明,因为6.7.5 "Declarators"表示将语法定义为:

    direct-declarator:
        [...]
        direct-declarator ( parameter-type-list )
        direct-declarator ( identifier-list_opt )
    

    所以只有标识符列表版本可以为空,因为它是可选的( _opt ) .

    direct-declarator 是唯一定义声明符的括号 (...) 部分的语法节点 .

    那么我们如何消除歧义并使用没有参数的更好的参数类型列表呢? 6.7.5.3函数声明符(包括原型)说:

    10 void类型的未命名参数作为列表中唯一项的特殊情况指定该函数没有参数 .

    所以:

    void f(void);
    

    就是这样 .

    这是一个明确允许的神奇语法,因为我们不能以任何其他方式使用 void 类型参数:

    void f(void v);
    void f(int i, void);
    void f(void, int);
    

    What can happen if I use an f() declaration?

    也许代码编译得很好:6.7.5.3函数声明符(包括原型):

    14函数声明符中不属于该函数定义的空列表指定不提供有关参数数量或类型的信息 .

    所以你可以逃脱:

    void f();
    void f(int x) {}
    

    其他时候,UB可以爬起来(如果你很幸运,编译器会告诉你),你将很难搞清楚原因:

    void f();
    void f(float x) {}
    

    见:Why does an empty declaration work for definitions with int arguments but not for float arguments?

    f() and f(void) for definitions

    f() {}
    

    VS

    f(void) {}
    

    是相似的,但不完全相同 .

    6.7.5.3函数声明符(包括原型)说:

    14函数声明符中的空列表是该函数定义的一部分,指定该函数没有参数 .

    看起来类似于 f(void) 的描述 .

    但仍然......似乎:

    int f() { return 0; }
    int main(void) { f(1); }
    

    符合未定义的行为,同时:

    int f(void) { return 0; }
    int main(void) { f(1); }
    

    不符合要求,如:Why does gcc allow arguments to be passed to a function defined to be with no arguments?

    TODO明白为什么 . 与原型有关还是与原型有关 . 定义原型 .

  • 91

    除了语法上的差异,许多人也更喜欢使用 void function(void) 出于实际原因:

    如果您正在使用搜索功能并想要找到该功能的实现,您可以搜索 function(void) ,它将返回原型以及实现 .

    如果省略了第二个 void ,则必须搜索 function() ,因此也会查找所有函数调用,这使得查找实际实现变得困难 .

相关问题