设置

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

第6段



如果表示被调用函数的表达式的类型为不包含原型,对每个参数执行整数提升,并将类型为float的参数提升为double。这些被称为默认参数提升。
如果参数数量不等于参数数量,则行为是不确定的。
如果函数的类型定义为包含原型,并且原型以省略号(, ...)结尾,或者升级后的参数类型与参数类型不兼容,则行为未定义。
如果函数的类型定义为不包含原型,并且升级后的参数类型与升级后的参数类型不兼容,行为是不确定的,但以下情况除外:


一个升级类型是有符号整数类型,另一个提升的类型是相应的无符号整数类型,并且值在两种类型中都可以表示;
两种类型均指向字符类型或void的合格或不合格版本的指针。






第7段



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




第8段




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



我所知道的


默认参数提升是charshortint / unsigned intfloatdouble

可变参数函数的可选参数(例如printf)受默认设置约束为了便于记录,我对函数原型的理解是:

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


问题

我我很难过所有这些。我有一些问题:


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


#1 楼

赞成AProgrammer的答案-那些是真正的商品。

对于那些想知道事情为什么如此的人:在1988年之前的黑暗时代,经典的功能原型没有这样的东西“提出了K&R” C和默认参数提升的原因,因为(a)本质上是“免费的”,因为在寄存器中放入一个字节比在寄存器中放入一个单词的花费更多,并且(b)减少关于参数传递中的潜在错误。第二个原因从未完全消除,这就是为什么在ANSI C中引入函数原型是C语言有史以来最重要的变化。

关于默认促销何时启动:默认参数促销确切地在参数的预期类型未知时使用,即在没有原型或参数为可变参数时。

评论


感谢您的澄清。回答“为什么”确实可以帮助我解决这一问题。

–安德鲁·基顿(Andrew Keeton)
09年8月10日在17:46

#2 楼


将具有原型的函数的(非可变参数)参数转换为相应的类型,可以是char,short,float。
不具有原型和可变参数的函数的参数将接受默认参数提升。 br />
如果您定义带有原型的函数,而没有原型就使用它,反之亦然,并且它具有char,short或float类型的参数,则在运行时可能会遇到问题。如果提升后的类型与读取参数列表时使用的类型不匹配,则可变参数函数会遇到相同类型的问题。

示例1:使用原型定义函数并使用时出现问题

definition.c

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


use.c

void f();

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


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

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

定义.c

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


(这是非常老式的定义)


void f(char c);

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


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

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

评论


当您说“如果声明带有原型的函数并在没有原型的情况下使用它……”是什么意思?

–安德鲁·基顿(Andrew Keeton)
09年8月10日在17:48

哦,我在定义时用了声明。修复并添加了示例。

– AProgrammer
09年8月10日在18:01

#3 楼

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

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


这是包含原型的函数声明。

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


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

“ Prototyped”和“ non-prototyped”只是一个属性。函数类型,以及声明和定义都介绍了函数的类型。

因此您可以在没有原型的情况下进行声明:

void func();


或者您可以不使用原型(K&R C样式)进行定义:

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