11.5 内存布局控制:repr(C) 与 repr(align)
在 Rust 中,结构体和枚举的默认内存布局是未指定的。编译器可以自由地重排字段、插入填充字节或优化对齐方式,以提升性能。这种灵活性对纯 Rust 程序有益,但在与外部系统(如 C 代码、硬件寄存器或网络协议)交互时,会导致兼容性问题。为此,Rust 提供了 #[repr(...)] 属性,用于显式控制类型的内存表示。
repr(C):C 兼容布局
#[repr(C)] 强制结构体或枚举按照 C 语言的规则进行内存布局:
- 字段按声明顺序排列;
- 对齐和填充遵循目标平台的 C ABI;
- 枚举的判别值(discriminant)从 0 开始递增,且每个变体占用相同大小(若无数据)。
示例:
#[repr(C)]
struct Point {
x: f32,
y: f32,
}
该结构体在 C 中可对应:
typedef struct {
float x;
float y;
} Point;
两者在内存中的字节布局完全一致,可安全通过 FFI 传递。
对于枚举:
#[repr(C)]
enum Status {
Idle = 0,
Running = 1,
Stopped = 2,
}
其大小为 4 字节(通常为 c_int 大小),且每个变体的判别值固定,便于 C 代码理解。
repr(transparent)
#[repr(transparent)] 是 repr(C) 的特例,用于定义与单个字段完全相同布局的新类型:
#[repr(transparent)]
struct Wrapper(i32);
// Wrapper 与 i32 在内存中完全等价
常用于创建类型安全的封装(如单位类型、FFI 包装器),同时保证零成本抽象。
repr(align(N)):强制对齐
某些硬件或协议要求数据按特定字节边界对齐(如 16 字节对齐用于 SIMD 指令)。#[repr(align(N))] 可强制类型满足最小对齐要求:
#[repr(align(16))]
struct AlignedBuffer {
data: [u8; 16],
}
此时,AlignedBuffer 的对齐值至少为 16,即使其内容本身只需 1 字节对齐。这会影响数组、栈分配和结构体嵌套时的布局。
可与其他 repr 组合使用:
#[repr(C, align(8))]
struct Header {
version: u8,
flags: u8,
length: u16,
}
该结构体既保持 C 布局,又确保整体 8 字节对齐。
repr(packed):禁用填充(谨慎使用)
#[repr(packed)] 移除结构体内部的所有填充字节,使字段紧密排列:
#[repr(packed)]
struct Packed {
a: u8,
b: u32, // 通常需 4 字节对齐,但 packed 下紧随 a
}
虽然节省空间,但可能导致未对齐访问,在某些架构(如 ARM)上引发硬件异常或严重性能下降。仅在与严格定义的二进制格式(如文件头、网络包)交互时使用,并避免直接解引用字段。
实际应用场景
- FFI 互操作:确保 Rust 结构体与 C 头文件定义一致;
- 嵌入式开发:映射内存映射寄存器(MMIO);
- 协议解析:直接将字节切片转为结构体(需配合
unsafe和对齐检查); - SIMD 或 GPU 编程:满足向量化指令的对齐要求。
例如,安全地将字节数组解释为 repr(C) 结构体:
#[repr(C)]
struct PacketHeader {
magic: u32,
size: u16,
}
fn parse_header(data: &[u8]) -> Option<PacketHeader> {
if data.len() < std::mem::size_of::<PacketHeader>() {
return None;
}
// 注意:还需检查对齐!此处简化处理
unsafe {
Some(std::ptr::read_unaligned(
data.as_ptr() as *const PacketHeader
))
}
}
小结
#[repr(C)]、#[repr(align(N))] 等属性赋予 Rust 精确控制内存布局的能力,是实现高效、可靠 FFI 和系统编程的关键工具。然而,这些特性通常需配合 unsafe 使用,开发者必须清楚对齐、填充和字节序等底层细节。合理使用 repr 属性,可以在不牺牲安全性的前提下,打通 Rust 与外部世界的二进制接口。