在 Rust 中,编译器努力高效地在内存中放置数据,利用关于对齐和结构及枚举布局的知识。这个问题在低级和系统开发中尤为重要,因为类型的过大尺寸会导致显著的内存浪费。
结构的自动对齐是大多数语言的特性,但在 Rust 中,编译器提供了关于布局的严格保证(同时允许其优化),并在枚举的情况下通过合并内存来实现紧凑存储,考虑到最大大小。
结构或枚举中字段的顺序和类型影响类型的最终大小,这与对齐特性有关。错误的顺序会增加“填充”——未使用的字节。对于带有关联数据的枚举,最大变体决定大小,而某些构造可能会使枚举意外变得庞大。
正确指定字段顺序并选择类型,通过 std::mem::size_of 分析数据的大小。对于枚举——对嵌套结构和指针要更加小心。
代码示例:
struct Bad { a: u8, // 占用 1 字节 + 7 字节填充 b: u64, // 占用 8 字节 } struct Good { b: u64, // 8 字节,开始时对齐 a: u8, // 1 字节 + 7 字节填充在末尾 }
检查大小:
use std::mem::size_of; println!("{}", size_of::<Bad>()); // 16 字节 println!("{}", size_of::<Good>()); // 16 字节(但填充现在在末尾)
对于枚举:
enum Example { Unit, Num(u32), Pair(u64, u8), } println!("{}", size_of::<Example>()); // 大小是最大(变体大小) + 判别符(discriminant)
关键特性:
如果在结构中交换 u8 和 u64 字段,结构的大小会改变吗?
不会,总大小仍将是最大字段对齐的倍数,但填充将发生移动。这很重要,如果结构包含在另一个结构中或者传递给 FFI。
小枚举可能占用大量内存吗?
是的,如果至少有一个变体包含一个大对象或引用,那么枚举的总大小将会是最“重”的变体加上判别符。
结构的布局在所有平台上是否总是相同?
不,布局和对齐可能在架构之间有所不同。为了严格控制,使用 repr(C) 属性。
#[repr(C)] struct MyFFIStruct { x: u32, y: u8, }
在大型集合中使用包含嵌套 Vec 的枚举,这种情况很少见,但会将枚举的大小增加 10 倍。内存被浪费。
优点:
缺点:
枚举被拆分成多个较小的枚举,数组/集合被单独存储或通过 Box 处理稀少的变体,布局通过 #[repr(C)] 控制。通过 size_of 检查尺寸。
优点:
缺点: