解引用强制转换为匿名结构体指针是否违反严格别名规则?兼询C结构体布局一致性
问题1:解引用强制转换为匿名结构体指针是否违反严格别名规则?
答案是肯定的,这种操作违反严格别名规则,会导致未定义行为。
先简单捋下严格别名规则的核心:C标准规定,不同类型的指针(除了几个明确的例外情况)不能用来访问同一块内存区域,否则编译器可能做出不符合预期的优化,程序行为完全不可控。
比如你写了这样的代码:
int num = 100; struct { int value; } *anon_ptr = (struct { int value; } *)# printf("%d", anon_ptr->value); // 这里就踩坑了
这里的匿名结构体struct { int value; }是一个全新的、独立的类型,和int类型既不是兼容类型,也不在严格别名规则允许的例外列表里(例外包括字符类型指针、指向共用体的指针、指向兼容类型的指针等)。用这个匿名结构体指针去解引用原本是int类型的内存,就违反了严格别名规则,属于标准意义上的未定义行为。
问题2:仅含相同唯一成员的不同结构体间强制转换是否安全?
这个问题要拆成标准规定和实际编译器行为两部分来说:
标准层面:不安全,违反严格别名规则
C标准只保证兼容类型的结构体布局完全一致。而什么是兼容类型?两个结构体要成为兼容类型,要么是同一个作用域下定义的同标签结构体,要么是通过标签引用的同一类型。像你提到的struct foo、struct bar,还有匿名的struct { char *id; },哪怕它们的成员列表完全一样,也属于不同的结构体类型,标准不认为它们是兼容类型。
从严格别名规则的角度看,直接把struct foo*强制转换成struct bar*然后解引用,本质是用一种结构体类型的指针去访问另一种结构体类型的对象,这不属于规则允许的例外情况,所以是违反严格别名规则的,行为未定义。
不过有个细节要注意:C标准明确规定,结构体第一个成员的地址和结构体对象的地址是相同的。但这只意味着你可以把结构体指针转换成它第一个成员的类型指针(比如把struct foo*转成char**)去访问成员,这不违反严格别名规则,但这和直接转换为另一个结构体指针是两码事。
实际编译器层面:多数情况能工作,但依赖非标准扩展
在实际开发中,很多主流编译器(比如GCC、Clang)对于这种“成员完全一致的结构体”会做兼容处理,不会因为类型不同就做破坏逻辑的优化。但这是编译器的非标准扩展,不是C标准保证的行为——哪天编译器更新了优化策略,或者你换了个编译器,程序可能就出问题了。
所以如果要保证代码的可移植性和标准合规性,最好不要这么干。如果一定要在类似场景下操作,建议用共用体(C标准允许通过共用体的不同成员访问同一块内存,这是严格别名规则的例外),或者先转换成字符类型指针再转目标类型(虽然这也不算完全合规,但比直接转结构体指针风险低一些)。
内容的提问来源于stack exchange,提问作者nebuch

