将const int*地址转为int**传参的C90代码是否存在未定义行为?
先看这段C代码:
#include <stdlib.h> int some_api_function_with_multiple_results( int** OUT_result ) { *OUT_result = malloc( 123 * sizeof(**OUT_result) ); /* 实际解引用操作 */ return 42; } int main(void) { const int* numbers; /* 别问为什么是const,只是好奇这种写法! */ int some_status = some_api_function_with_multiple_results( (int**)&numbers ); free( (void*)numbers ); return some_status; }
这段代码中,const int*类型的指针(注意不是int* const)被通过引用传递给了一个期望接收int**类型作为输出参数的函数。它在GCC 9.4.0的C90模式(-std=c90)下可以正常编译且无警告。
相关C标准条款分析
C90标准关于指针表示的规定
6.1.2.5 类型
void类型指针与字符类型指针的表示和对齐要求相同。类似地,指向兼容类型的限定或非限定版本的指针,其表示和对齐要求也相同。¹⁶ 其他类型的指针则无需具备相同的表示或对齐要求。¹⁶ 相同的表示和对齐要求意味着它们可作为函数参数、函数返回值及联合成员互换使用。
注意:早期C89草案曾有更严格的约束,但正式的C89文档与C90表述一致,属于草案误差:
3.1.2.5 类型
void类型指针与字符类型指针的表示和对齐要求相同。其他类型的指针则无需具备相同的表示或对齐要求。
指针隐式转换的合法规则
我们知道T*类型的值可以直接赋值给const T*类型变量或作为函数参数传递,无需强制转换,这符合以下C标准规则:
6.3.16.1 简单赋值
需满足以下条件之一:⁵¹
- 两个操作数均为指向兼容类型的限定或非限定版本的指针,且左操作数指向的类型具备右操作数指向类型的所有限定符;
⁵¹ 这些关于类型限定符的约束呈现不对称性,是由于将左值转换为“表达式的值”时(如6.2.2.1所述)会移除表达式顶层类型的所有限定符。
6.3.2.2 函数调用
若表示被调用函数的表达式包含原型,则参数会被隐式转换,如同通过赋值转换为对应参数的类型。
判断未定义行为的核心标准依据
但const int*与int*类型在C90、C89及C99标准中均不兼容,结合以下条款,我们可以明确代码的问题:
6.5.4.1 指针声明符
两种指针类型兼容的前提是,二者具备完全相同的限定符,且均指向兼容类型。
6.5.3 类型限定符
两种限定类型兼容的前提是,二者均为兼容类型的完全相同限定版本;类型限定符在说明符或限定符列表中的顺序不影响指定类型。
6.1.2.6 兼容类型和复合类型
引用同一对象或函数的所有声明必须具备兼容类型;否则行为未定义。
结论
这段代码存在未定义行为,原因如下:
- 代码将
const int*的地址强制转换为int**并传入函数,函数内部通过*OUT_result赋值,本质是用int**类型的指针修改const int*类型的对象。 - 根据标准,
int**和const int**属于不兼容的指针类型(因为它们指向的int*和const int*类型不兼容),通过不兼容类型的指针访问对象,符合标准中“行为未定义”的判定条件。 - 虽然C90规定指向兼容类型的限定/非限定版本的指针表示和对齐要求相同,但这仅保证它们可以在参数、返回值等场景下互换,并不允许通过不兼容指针类型修改对象——标准明确要求引用同一对象的声明必须是兼容类型,否则行为未定义。
内容的提问来源于stack exchange,提问作者cher-nov

