38

设置

我对在 C 中调用函数时的默认参数提升有几个问题。这是C99 标准 (pdf)中的第 6.5.2.2 节“函数调用”第 6、7 和 8 段(强调添加并分解为列表以便于阅读):

第 6 段

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

第 7 段

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

第 8 段

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

我知道的

  • 默认参数提升是charand shortto int/ unsigned intand floattodouble
  • 可变参数函数的可选参数(如printf)受默认参数提升的约束

作为记录,我对函数原型的理解是这样的:

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

问题

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

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

3 回答 3

40

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

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

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

于 2009-08-10T17:23:50.643 回答
36
  • 具有原型的函数的(非可变参数)参数被转换为相应的类型,可以是 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。

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

于 2009-08-10T16:17:40.293 回答
17

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

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

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

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

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

“原型”和“非原型”只是函数类型的属性,声明和定义都引入了函数的类型。

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

void func();

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

void func(a, b, c)
    int a;
    char b;
    float c;
{ /* ... */ }
于 2009-08-11T00:30:45.560 回答