不包含原型的函数类型的兼容性

Compatibility of function types that does not include a prototype

有函数类型兼容性规则 N2310 6.7.6.3(p15):

If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, the parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions.

我可以想象一个例子:

#include <stdio.h>

int foo();

float bar();

int main(void){
    printf("%d\n", foo(1, 3)); //fine, int is unchanged by default argument promotion
    printf("%f\n", bar(1.0f, 2.0f)); //error, float is promoted to double
}

int foo(int a, int b){
    return a + b;
}

float bar(float b, float c){
    return b + c;
}

我发现矛盾的是 6.5.2.2(p6) 提到:

If the number of arguments does not equal the number of parameters, the behavior is undefined.

int foo() 的情况下,它有一个空的 identifier-list。那么调用 printf("%d\n", foo(1, 3)); 是否会产生 UB(提供了 2 个参数)?

无论如何,规则看起来很奇怪,有点不自然。那是什么原因呢?我想与以前版本的标准有一定的向后兼容性...?

C 2018 6.7.6.3 15 告诉你两种类型是否兼容。所以它可以用来比较两个声明。对于 foo,您有:

int foo(); int foo(int a, int b) {...}

其中,第二个有一个参数列表,第一个由函数声明指定,该声明不是函数定义的一部分,并且包含一个空的标识符列表。所以规则是 6.7.6.3 15 适用。它说参数列表不应有省略号终止符(实际上没有),并且每个参数的类型应与默认参数提升产生的类型兼容(它们是,因为 int 产生int).

然后,对于 bar,我们有:

float bar();
float bar(float b, float c) {...}

同样,6.7.6.3 15 适用。但在这种情况下,每个参数的类型都不是默认参数提升的结果,因为默认提升会将 float 转换为 double。所以这两个声明声明 bar 具有不兼容的类型。

关于 6.5.2.2 6:

… If the number of arguments does not equal the number of parameters, the behavior is undefined…

这是指实际函数的参数个数,而不是声明中出现在(空)列表中的参数个数。

Anyway the rules look pretty strange and kind of unnatural. What was the reason for that? I suppose some backward compatibility with previous versions of the Standard... ?

是的,C 最初对函数声明很宽松,允许使用空参数列表声明函数,如果我没记错的话,所有参数都以提升的类型传递。后来支持更严格和更精确的声明,编写规则是为了让旧代码继续工作。

请注意,有关函数类型兼容性的规则与函数的声明有关,但与调用无关。

当编译器正在分析函数调用时,6.5.2.2 中的规则用于准备调用。这些规则规定,根据在调用点可见的函数声明,以各种方式处理参数。 (从技术上讲,指向表示被调用函数的表达式类型。这通常是函数名称,但也可以是指向函数的指针,包括由强制转换表达式计算的函数。)

关于兼容性的规则向您保证,如果您使用与实际函数定义的类型兼容的类型调用函数,那么该调用具有定义的行为。