将union成员指针传入union类型不可见的函数是否符合C17标准?
结论
这段代码属于C标准定义的未定义行为,实际运行得到预期结果只是特定编译器、特定编译选项下的巧合,不代表行为合法。
核心规则依据
问题的核心是C17标准规定的严格别名规则(6.5节第7段),要求对象的存储值只能通过以下几类左值访问:
- 和对象有效类型兼容的类型
- 有效类型的限定版本
- 有效类型对应的有符号/无符号版本
- 包含上述类型作为成员的聚合或union类型的左值
注意:上述最后一条规则的适用前提是,访问左值的上下文可以看到对应聚合/union的完整定义。
问题代码违反规则的原因
#include <stdio.h> struct t1 { int m; }; struct t2 { int m; }; int f(struct t1 *p1, struct t2 *p2){ if (p1->m < 0) p2->m = -p2->m; return p1->m; } int g(){ union U{ struct t1 s1; struct t2 s2; } u = {-1}; return f(&u.s1, &u.s2); } int main(){ int i = g(); printf("%d\n",i); }
- 在函数
g中,union对象u的有效初始化成员是s1(类型为struct t1),对应存储的有效类型是struct t1。 - 将
&u.s1和&u.s2两个指针传入函数f的操作本身符合标准要求,但在f的上下文中,看不到union U的完整定义,因此不能触发上述聚合/union类型的别名豁免规则。 - 在
f中通过struct t2类型的指针p2修改存储,又通过struct t1类型的指针p1读取存储,直接违反严格别名规则,属于未定义行为。
可正常运行的原因
主流编译器(GCC、Clang等)默认编译选项没有启用最严格的别名优化,且本例中struct t1和struct t2的内存布局完全一致,修改p2->m刚好会落到p1->m对应的内存地址上,因此会得到预期结果。如果启用高优化等级(如-O2 -fstrict-aliasing),编译器会基于严格别名规则判定p1和p2指向不相关的对象,直接将返回值优化为初始值-1,运行结果就会不符合预期。
内容的提问来源于stack exchange,提问作者Vincent
相关产品推荐
相关产品推荐

