Clang/GCC是否认为指向结构体成员的指针可别名不同类型成员?
结构体成员指针的别名问题分析
先看这个示例代码,编译器似乎认为传递给bar()的double类型指针可能会修改结构体里的整型成员a:
struct A { int a; double b; }; void bar(double*); int foo() { struct A a; a.a = 9; bar(&a.b); return a.a; }
Clang生成的汇编代码如下:
stp x29, x30, [sp, #16] add x29, sp, #16 mov w8, #9 str w8, [sp] mov x8, sp # 保存变量地址 add x0, x8, #8 bl bar(double*) ldr w0, [sp] # 重新读取a.a的值 ldp x29, x30, [sp, #16] add sp, sp, #32 ret
如果把整型变量定义在结构体外部,就不会出现这种重新读取的情况。看起来GCC和Clang都认为bar(double*)有可能通过&A::b修改A::a,但直觉上double*不能别名int*,这种直觉到底对不对?如果正确,原因是什么?
核心结论:直觉不完全准确,要从C标准规则和编译器假设解释
严格别名规则的边界情况
C标准的严格别名规则确实规定:不同类型的指针(除char*/unsigned char*等少数例外)不能直接访问其他类型对象,否则行为未定义。但这里的场景不是直接用double*访问int,而是编译器必须考虑函数可能通过合法类型转换操作整个结构体。结构体布局连续性带来的操作可能
结构体成员按声明顺序连续布局(对齐规则由实现定义,但顺序固定)。&a.b是指向结构体成员b的指针,函数bar完全可以通过以下合法操作修改a.a:
#include <stddef.h> void bar(double* ptr) { // 通过char*调整指针到结构体起始地址,再转换回struct A* struct A* a_ptr = (struct A*)((char*)ptr - offsetof(struct A, b)); a_ptr->a = 0; }
这种操作符合C标准:char*允许访问任意类型内存,通过偏移量计算结构体起始地址的行为是明确的。
- 编译器的保守假设
编译器无法预知bar的具体实现,因此必须做最坏假设:bar可能通过传入的double*,借助结构体布局特性修改同结构体里的其他成员。所以编译器不能把a.a的值保存在寄存器里复用,必须在调用bar后重新从内存读取——这就是汇编里"重新读取"操作的原因。
而如果整型变量和double变量是独立的(不在同一个结构体里),编译器可以确定它们的内存地址不重叠(除非显式用指针操作重叠内存,但那属于未定义行为),因此bar的double*无法合法修改独立的整型变量,编译器就可以安全地把整型值存在寄存器里,不需要重新读取。
内容的提问来源于stack exchange,提问作者Bikineev
相关产品推荐
相关产品推荐

