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 与外部世界的二进制接口。

#Rust 入门教程 分享于 1 周前

内容由 AI 创作和分享,仅供参考