我阅读了标准N1570部分6.5.2.2 Function calls
,并对包含原型的函数类型的特殊含义感到困惑。恰恰6.5.2.2(p6)
如果使用不包含原型的类型定义函数,并且促销后的参数类型与促销后的参数类型不兼容,则行为未定义,但以下情况除外:
- 一个提升类型是有符号整数类型,另一个提升类型是相应的无符号整数类型,并且该值可在两种类型中表示;
- 两种类型都是指向字符类型或void的限定或非限定版本的指针。
6.5.2.2(p7)
提供了一个使用原型调用函数的规则:
如果表示被调用函数的表达式具有包含原型的类型,则将参数隐式转换为相应参数的类型,就像通过赋值一样,将每个参数的类型作为其声明的非限定版本类型。
请考虑以下示例:
struct test_arg{
int a;
};
void test_no_prototype(const struct test_arg a){ }
void test_with_prototype(const struct test_arg a);
void test_with_prototype(const struct test_arg a){ }
int main(){
struct test_arg test = {.a = 42};
test_no_prototype(test); //1 UB?
test_with_prototype(test); //2 Fine?
}
我认为1是UB,因为test_no_prototype
不包括原型而且test
有不合格版本的struct test_arg
,但是该论证的类型为const struct test_arg
,由于资格不同而与struct test_arg
不兼容。
我认为2很好,因为test_with_prototype
包含原型,6.5.16.1(p1)
的简单赋值约束允许从同一结构的非限定版本赋值给合格的struct类型的变量。
这看起来很奇怪,而且现在我无法想象为什么我们以不同的方式对待有和没有原型的函数。可能我错误地理解了规则...如果是这样,你能解释它的含义吗?
术语原型并不意味着在其定义之前声明函数。它表示声明其参数类型的函数声明(C 2018 6.2.1 2)。
test_no_prototype
有一个原型,因为void test_no_prototype(const struct test_arg a){ }
声明了它的参数类型const struct test_arg
。
没有原型的声明的一个例子是void test_no_prototype();
。它是一种旧式声明,不应在新代码中使用。