未知结构体其他成员时访问首个成员是否为UB?含标准及编译器疑问
关于结构体类型转换的未定义行为疑问
我怀疑这段代码属于ANSI标准下的未定义行为(UB),但在我的x64 Linux系统中,即使开启-Wall -Wextra -Werror -O3编译选项,gcc和clang编译器都能得到预期结果。代码如下:
#include <stdio.h> typedef struct { int tag; } base; typedef struct { base b; int x; } derived1; typedef struct { base b; int x, y; } derived2; int sum(void* p) { base* b = (base*)p; if(b->tag) { derived2* d = (derived2*)b; return d->x + d->y; } else { derived1* d = (derived1*)b; return d->x; } } int main(void) { derived1 d1; d1.b.tag = 0; d1.x = 2; derived2 d2; d2.b.tag = 1; d2.x = 3; d2.y = 4; printf("%d %d\n", sum(&d1), sum(&d2)); }
问题
- 按照ANSI标准,上述代码是否属于未定义行为(UB)?
- 在gcc和clang编译器中,上述代码是否也属于未定义行为?
- 使用
-fno-strict-aliasing或其他编译选项会产生什么影响?
解答
1. ANSI标准下的判定
根据ANSI C(C89)及后续C标准(如C99、C11),这段代码不属于未定义行为。标准明确规定:指向结构体对象的指针可以转换为指向其第一个成员的指针,反之亦然,且转换前后的指针指向同一内存地址。
代码中derived1和derived2的第一个成员都是base类型,因此将derived1*/derived2*转换为base*完全合法;后续通过tag字段判断原类型,再将base*转换回对应的派生结构体指针,这种操作符合标准要求,不存在未定义行为。
2. gcc和clang中的判定
在gcc和clang中,这段代码同样不属于未定义行为。两个编译器都严格遵循C标准的结构体指针转换规则,对这种基于第一个成员的类型转换有明确支持。即使开启-Wall -Wextra -Werror -O3这类严格选项,编译器也不会将其判定为UB,你能得到预期结果正是因为编译器正确处理了这种合法操作。
3. 编译选项的影响
-fno-strict-aliasing:该选项会关闭编译器的严格别名优化。但这段代码的类型转换完全符合标准允许的别名规则(结构体与其第一个成员可互相别名),所以开启或关闭此选项对代码正确性无影响。如果代码存在其他违反严格别名规则的操作,关闭该选项会让编译器放弃基于别名规则的优化,避免因优化触发UB表现。- 默认的
-fstrict-aliasing:该选项默认开启,编译器会严格遵循别名规则进行优化,而这段代码符合规则,因此默认优化下不会出现问题。 -O0(无优化):此模式下编译器行为更直观,但同样不会改变代码的合法性,运行结果与优化模式一致。
内容的提问来源于stack exchange,提问作者MWB
相关产品推荐
相关产品推荐

