Rust中#[repr(C)]结构体首成员指针转结构体指针是否合法?
问题说明
在C语言中,指向结构体的指针可转换为指向其第一个成员的指针,反之亦然,即结构体的地址被定义为其第一个成员的地址。这种写法通常用于实现C++风格的继承机制。
对应的C示例代码:
struct Base { int x; }; struct Derived { struct Base base; int y; }; int main() { struct Derived d = { {5}, 10 }; struct Base *base = &d.base; // OK printf("%d\n", base->x); struct Derived *derived = (struct Derived *)base; // OK printf("%d\n", derived->y); }
对应的疑问是:如果Rust中的结构体标注了repr(C)(以此保证结构体字段不会被重排),是否允许执行同样的指针转换操作?
给出的Rust测试代码如下:
#[derive(Debug)] #[repr(C)] struct Base { x: usize, } #[derive(Debug)] #[repr(C)] struct Derived { base: Base, y: usize, } // safety: `base` should be a reference to `Derived::base`, otherwise this is UB unsafe fn get_derived_from_base(base: &Base) -> &Derived { let ptr = base as *const Base as *const Derived; &*ptr } fn main() { let d = Derived { base: Base { x: 5 }, y: 10, }; let base = &d.base; println!("{:?}", base); let derived = unsafe { get_derived_from_base(base) }; // defined behaviour? println!("{:?}", derived); }
上述代码可正常运行,需要确认该写法是否能始终正常工作,是否属于Rust中已定义的行为(不存在未定义行为UB)。
回答
- 标注
#[repr(C)]的Rust结构体,从指向第一个成员的指针转换回外层结构体指针的写法属于明确定义的合法行为,不存在未定义风险,可以稳定使用。 - 这个保证是Rust对C ABI兼容性承诺的一部分:
#[repr(C)]会强制结构体完全遵循C语言的内存布局规则:- 结构体的起始地址和其第一个字段的起始地址完全重合,没有任何前置偏移
- 字段严格按照声明顺序排列,编译器不会做重排优化
- 内存对齐规则和对应平台的C ABI完全一致
- 使用时必须严格满足以下安全前提,否则依然会触发未定义行为:
- 传入的
&Base必须确实是某个Derived实例内部的base字段的引用,不能是独立存在的Base实例,也不能是其他结构体类型中嵌入的Base字段,否则转换后访问y字段会出现内存越界 - 转换全程必须遵守Rust的引用别名规则,同一时刻不能存在违反可变/不可变引用独占性要求的重叠引用
- 未标注
#[repr(C)]的默认Rust结构体(采用repr(Rust)布局)不提供任何字段顺序、偏移位置的保证,绝对不能执行这类指针强转操作。
- 传入的
内容的提问来源于stack exchange,提问作者cry0genic
相关产品推荐
相关产品推荐

