【问题标题】:Default argument promotions in C function callsC 函数调用中的默认参数提升
【发布时间】:2010-11-18 08:27:40
【问题描述】:

设置

我对在 C 中调用函数时的默认参数提升有几个问题。这是C99 standard (pdf) 中的第 6.5.2.2 节“函数调用”第 6、7 和 8 段(为方便起见,添加了重点并将其分解为列表阅读):

第 6 段

  1. 如果表示被调用函数的表达式的类型不包含原型,则对每个参数执行整数提升,并且类型为 float 的参数提升为 @987654323 @。这些称为默认参数提升
  2. 如果参数的数量不等于参数的数量,则行为未定义。
  3. 如果函数使用包含原型的类型定义,并且原型以省略号 (, ...) 结尾,或者提升后的参数类型与类型不兼容在参数中,行为未定义。
  4. 如果函数定义的类型不包含原型,并且提升后的参数类型与提升后的参数类型不兼容,则行为未定义,除了对于以下情况:
    • 一种提升类型是有符号整数类型,另一种提升类型是对应的无符号整数类型,并且值在两种类型中都是可表示的;
    • 这两种类型都是指向字符类型或void 的限定或非限定版本的指针。

第 7 段

  1. 如果表示被调用函数的表达式的类型确实包含原型,则参数将隐式转换为相应参数的类型,就像通过赋值一样,采用每个参数都是其声明类型的非限定版本。
  2. 函数原型声明器中的省略号表示法会导致参数类型转换在最后一个声明的参数之后停止。 默认参数提升是在尾随参数上执行的。

第 8 段

  1. 没有隐式执行其他转换;特别是,参数的数量和类型不会与不包含函数原型声明符的函数定义中的参数的数量和类型进行比较。

我知道的

  • 默认参数提升charshortint/unsigned intfloatdouble
  • 可变参数函数的可选参数(如printf)受默认参数提升的约束

郑重声明,我对函数原型的理解是这样的:

void func(int a, char b, float c);  // Function prototype
void func(int a, char b, float c) { /* ... */ }  // Function definition

问题

我很难理解这一切。以下是我的一些问题:

  • 原型化函数和非原型化函数的行为真的差别很大吗,例如在默认提升和隐式转换方面?
  • 默认参数提升何时发生?总是这样吗?或者只是在特殊情况下(比如可变参数函数)?是否取决于函数是否原型化?

【问题讨论】:

    标签: c function prototype promotions


    【解决方案1】:
    • (非可变参数)带有原型的函数的参数转换为相应的类型,可以是char、short、float。

    • 没有原型和可变参数的函数的参数受默认参数提升的约束。

    如果您使用原型定义函数并在没有原型的情况下使用它,反之亦然,并且它具有 char、short 或 float 类型的参数,您可能会在运行时遇到问题。如果提升的类型与读取参数列表时使用的类型不匹配,您将遇到与可变参数函数相同的问题。

    示例 1:使用原型定义函数和不使用原型时的问题。

    定义.c

    void f(char c)
    {
       printf("%c", c);
    }
    

    使用.c

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

    可能会失败,因为将传递一个 int 而函数需要一个 char。

    示例 2:定义没有原型的函数并使用原型时出现的问题。

    定义.c

    void f(c)
       char c;
    {
       printf("%c", c);
    }
    

    (这种定义很老套)

    使用.c

    void f(char c);
    
    int main()
    {
       f('x');
    }
    

    可能会失败,因为需要一个 int 但会传递一个 char。

    注意:您会注意到标准库中的所有函数都具有由默认提升产生的类型。因此,在添加原型时,它们不会在过渡期间造成问题。

    【讨论】:

    • 当你说“如果你声明一个带有原型的函数并在没有原型的情况下使用它......”是什么意思?
    • 哦,当我的意思是定义时,我使用了声明。修复并添加了一个示例。
    【解决方案2】:

    您的困惑源于对术语的轻微误解 - 声明和定义都可以包含原型(或不包含):

    void func(int a, char b, float c);
    

    这是一个包含原型的函数声明

    void func(int a, char b, float c) { /* ... */ }
    

    这是一个包含原型的函数定义

    “Prototyped”和“non-prototyped”只是函数的属性type,声明和定义都引入了函数的类型。

    所以你可以有一个没有原型的声明:

    void func();
    

    或者你可以有一个没有原型的定义(K&R C 风格):

    void func(a, b, c)
        int a;
        char b;
        float c;
    { /* ... */ }
    

    【讨论】:

      【解决方案3】:

      赞成 AProgrammer 的回答——那些是真正的商品。

      对于那些想知道为什么事情会这样的人:在 1988 年之前的黑暗时代,经典的“K&R”C 中没有函数原型和默认参数这样的东西进行促销是因为 (a) 本质上是“免费的”,因为将一个字节放入寄存器并不比将一个字放入寄存器中花费更多,并且 (b) 减少参数传递中的潜在错误。第二个原因从未完全解决,这就是为什么在 ANSI C 中引入函数原型是 C 语言中最重要的变化。

      至于默认提升何时生效:默认参数提升仅在参数的预期类型为未知时使用,也就是说,当没有原型或当参数是可变参数时。

      【讨论】:

      • 感谢您的澄清。回答“为什么”确实有助于我弄清楚这一点。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-02
      • 1970-01-01
      • 2020-09-01
      • 1970-01-01
      • 2012-11-20
      • 2015-05-01
      • 2016-12-12
      相关资源
      最近更新 更多