C函数调用中的默认参数提升

时间:2009-08-10 16:08:54

标签: c function prototype promotions

设置

在C中调用函数时,我有一些关于默认参数提升的问题。这里是6.5.2.2节“函数调用”C99 standard (pdf)中的第6,7和8段(强调添加并分为列表为了便于阅读):

第6段

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

第7段

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

第8段

  
      
  1. 不会隐式执行其他转换;特别是,参数的数量和类型不与功能定义中参数的参数进行比较,不包括函数原型声明器
  2.   

我所知道的

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

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

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

问题

我很难完成这一切。以下是我的一些问题:

  • 原型和非原型函数的行为真的有很大差异,例如关于默认促销和隐式转换吗?
  • 何时进行默认参数促销?它总是吗?或者它只是在特殊情况下(如可变函数)?是否取决于函数是否为原型?

3 个答案:

答案 0 :(得分:33)

    具有原型的函数的
  • (非变量函数)参数将转换为相应的类型,可以是char,short,float。

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

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

示例1:使用原型定义函数并在没有原型的情况下使用它时出现问题。

definition.c

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

use.c

void f();

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

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

示例2:定义没有原型的函数并将其与一个函数一起使用时出现问题。

definition.c

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

(这种定义很老式)

use.c

void f(char c);

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

会失败,因为期望int,但会传递char。

注意:您会注意到标准库中的所有功能都具有默认促销产生的类型。因此,在添加原型的过渡期间,它们不会引起问题。

答案 1 :(得分:33)

Upvoted AProgrammer的答案 - 那些是真货。

对于那些想知道为什么事情是这样的人:在1988年以前的黑暗时代,在经典的“K& R”C中没有功能原型这样的东西,建立默认参数促销是因为(a)基本上是“自由的”,因为在寄存器中放置一个字节而不是将一个字放入寄存器中,以及(b)减少参数传递中的潜在错误。第二个原因从未完全消除它,这就是为什么在ANSI C中引入函数原型是C语言中有史以来最重要的变化。

关于默认促销活动何时开始:默认参数促销活动恰好用于参数的预期类型未知,也就是说当没有原型或当参数是variadic时。

答案 2 :(得分:15)

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

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;
{ /* ... */ }