C语言中的默认参数和参数提升

6
我正在学习默认参数提升,但在某个点上卡住了。在 C 2011(ISO/IEC 9899:2011)中,相关部分似乎是:
§6.5.2.2 函数调用
如果表示所调用函数的表达式具有不包括原型的类型,则对每个参数执行整数提升,并将类型为 float 的参数提升为 double。这些被称为默认参数提升。如果参数的数量不等于参数的数量,则行为未定义。如果使用包含省略号 (,...) 的原型结束,或者在提升后的参数类型与参数类型不兼容的情况下定义该函数,则行为未定义。如果使用不包括原型的类型定义该函数,并且在提升后的参数类型与提升后的参数类型不兼容,则行为未定义,以下情况除外:
- 一个提升的类型是有符号整数类型,另一个提升的类型是相应的无符号整数类型,并且值在两种类型中都可表示; - 两种类型都是字符类型或 void 的限定或非限定版本的指针。
在段落的最后三行中,它谈到了不包括原型的函数类型在定义时。它说如果“提升后的参数类型与提升后的参数类型不兼容,则行为未定义”。
现在我有一个非常愚蠢的疑问,即如果函数声明和函数定义都不包括原型,就像本段落中所述,那么在最后三行中他们谈论哪些参数。而“参数提升”是什么意思,因为我只学过“参数提升”。这里的“参数提升”是什么意思?
此外,您可以举例说明上述例外情况吗?如果有人能用适当的示例解释这一点,那将非常可赞赏。
1个回答

6

在C语言标准化之前(即 C89 之前),函数的定义方式是不同的。为了向后兼容,这种风格仍然被支持于 C11 中。除非完全出于娱乐目的,否则不要使用它:

int add_ints(); //forward-declaration has no parameters

add_ints(a, b)
//implicit type for return and parameters is int, this only works in pre-standard C or C89/C90
//int a, b; //remove this comment in C99/C11 for it to compile (also add return type int)
{
    return a + b; //side note: old K&R compilers required parantheses around the return expression
}

在某种程度上,这些函数具有类似于varargs的行为的参数。调用方不知道函数需要哪些参数(与varargs相同)。它可以传递任何数量和任何类型的参数。然而,如果在调用语句中的参数数量与声明中的参数数量不匹配,那么这当然是未定义的行为。
当然,这会带来一个问题。如果调用方想传递一个short,它如何知道函数是否需要一个short(并直接传递它)或者需要将其转换为int?它无法知道,所以达成了一个共识。已经决定:
- char和short会升级为int - float会升级为double 所有按照K&R风格定义的函数以及varargs参数都会这样处理。这样,K&R函数将不会期望一个short参数,因此编译器总是会将short参数升级为int。
当然,正如@aschepler所说,您仍然可以像以下方式定义函数:
short add_shorts(a, b)
    short a, b;
{
    return a + b;
}

这意味着先将参数转换为int并传递给函数,然后函数才将它们转换为short并相加。

使用类似于printf()的函数时要小心:

printf("%.f", 3); //passes an int: UB and also wrong answer (my compiler prints 0)
printf("%.f", 3.0); //correct
printf("%.f", (double)3); //correct

您可能经常看到K&R函数,特别是如果作者没有注意为不带参数的函数添加void关键字:

int f1() //K&R function
{
    return 0;
}
int f2(void) //Standard function
{
    return 0;
}

int main(void) //Don't forget void here as well :P
{
    int a = f1(); //Returns 0
    int b = f2(); //Returns 0
    int c = f1(100); //UB - invalid number of parameters, in practice just returns 0 :)
    int d = f2(100); //Compiler error - parameter number/types don't match

    //A good compiler would give a warning for call #3, but mine doesn't :(
}

编辑:不确定为什么,但cppreference将像f1()这样定义的函数归类为它们自己的函数类型(无需参数,也没有void),而不是K&R函数。我手边没有标准文件,但即使标准文件也应该有相同的规定,它们应该具有我提到的历史记录,并且具有相同的行为。

C中的默认参数提升

C中的函数声明


注意:cppreference不是智慧或真理的来源。 - wildplasser
如果一个函数没有原型,那么它就是K&R风格的函数。我猜你以为它是一个没有参数的函数? - DarkAtom
@DarkAtom 不,没有原型,所有的东西(包括返回值和参数)都被假定为 int - wildplasser
参数是原型中的那些(或K&R伪原型)。实际传递的是参数。更新了我的答案。看一下printf()的例子。我传递了一个int,但函数期望一个double。整数与浮点数不兼容,因此会发生UB,因为默认参数转换不会将int转换为我们想要的double,而是保持不变。在add_shorts的例子中,参数提升传递了一个与short(函数所期望的)二进制兼容的int,因此没有UB。 - DarkAtom
1
是的,K&R风格的函数,但也有可变参数。基本上,标准以非常花哨的方式表达了这样一种情况:如果传递给函数的参数与其预期不符(例如传递一个int而不是double),那么这将被视为未定义行为。这是未定义行为,因为编译器无法知道函数需要什么参数(这些信息既不包含在K&R函数中,也不包含在可变参数省略号中)。 - DarkAtom
显示剩余18条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接