关于C99标准中参数调整相关未定义行为的技术问询
先把核心概念和规则拆解清楚,帮你理清这个技术点:
1. 对象类型vs函数类型的本质区别
根据C99标准,对象类型指的是除函数类型、void类型之外的所有类型——简单说,对象是能存储值的内存区域,对应的类型就是对象类型。而函数类型描述的是函数的签名(返回值+参数列表),它不属于对象类型,因为函数本身不是可存储值的内存区域(但指向函数的指针是对象类型,因为指针是可以存储的地址值)。
2. 函数参数的两种合法调整规则
C99对函数参数的类型调整是强制且合法的,两种核心场景:
- 数组类型参数:会被调整为指向数组元素类型的指针(比如
int arr[]调整为int *arr) - 函数类型参数:会被调整为指向该函数类型的指针(比如
void func()调整为void (*func)())
这两种调整后的类型都是对象类型(指针属于对象类型),所以完全合法,不会触发未定义行为。
3. 触发未定义行为的真正场景
你提到的6.9.1条款的未定义行为,是指调整后的参数类型既不是对象类型,也不属于上面两种合法调整后的指针类型。最典型的情况是:
- 参数类型为
void(注意:void作为唯一参数时是表示无参数的特殊语法,但如果显式声明void类型的带名参数,就会触发问题)
示例1:void类型的带名参数(未定义行为)
// 未定义行为:调整后的参数类型是void,不属于对象类型 void foo(void x) { // 任何操作都是UB,因为x的类型是void,无法被访问或使用 }
这里函数定义的参数x被声明为void类型,调整后的类型仍然是void——而void不是对象类型,直接违反6.9.1的规定,行为完全未定义(编译器可能报错,也可能生成奇怪的代码,运行时可能崩溃)。
示例2:函数类型参数的合法场景(无未定义行为)
你可能误以为函数类型参数调整后不是对象类型,但实际上下面的代码是完全合法的:
void bar(void (*fp)()); // 函数原型,参数是函数指针 // 函数定义中参数是函数类型,会被调整为函数指针(对象类型),合法 void bar(void fp()) { fp(); // 调用函数,合法 } void test() { printf("Hello\n"); } int main() { bar(test); // 传递函数名,隐式转为函数指针,合法 return 0; }
这个例子中,bar的定义参数void fp()会被自动调整为void (*fp)()(指向函数的指针),属于对象类型,所以完全符合标准,没有未定义行为。
规则背后的逻辑
C99要求函数参数必须是能被存储或传递的类型——对象类型(包括指针)可以通过值传递,而void类型或未调整的函数类型无法被作为参数值传递,因为它们没有可存储的“值”。因此,一旦调整后的参数类型不属于对象类型,标准就将其定为未定义行为,编译器可以自由处理这种非法情况。
内容的提问来源于stack exchange,提问作者Guillaume Petitjean

