Rust中Option的内存开销为何不恒定?编译器采用何种表示?
Rust中
Option<T>的内存表示与开销差异解析 测试代码与运行结果
先看用于验证Option<T>内存大小的Rust代码:
use std::mem; fn main() { println!("size Option(bool): {} ({})", mem::size_of::<Option<bool>>(), mem::size_of::<bool>()); println!("size Option(u8): {} ({})", mem::size_of::<Option<u8>>(), mem::size_of::<u8>()); println!("size Option(u16): {} ({})", mem::size_of::<Option<u16>>(), mem::size_of::<u16>()); println!("size Option(u32): {} ({})", mem::size_of::<Option<u32>>(), mem::size_of::<u32>()); println!("size Option(u64): {} ({})", mem::size_of::<Option<u64>>(), mem::size_of::<u64>()); println!("size Option(u128): {} ({})", mem::size_of::<Option<u128>>(), mem::size_of::<u128>()) }
在64位机器上运行后得到输出:
size Option(bool): 1 (1) size Option(u8): 2 (1) size Option(u16): 4 (2) size Option(u32): 8 (4) size Option(u64): 16 (8) size Option(u128): 24 (16)
核心问题解析
1. 为什么不用固定1字节标签?
这涉及两个关键因素:空值优化和内存对齐要求。
空值优化(Null Pointer Optimization)
Rust编译器会自动对Option<T>做优化:如果T类型存在未被使用的位模式(即某个二进制值不是T的有效实例),编译器就会用这个无效值来表示None,完全不需要额外的标签字节。
比如bool类型仅定义了true(1)和false(0)两个有效值,但它占1字节(可存储0-255),所以编译器可以用2这个值表示Option<bool>的None,让Option<bool>的大小和bool完全一致,没有额外开销。
但像u8、u16这类整数类型,所有可能的位模式都是有效值(比如u8的0-255都是合法的u8值),编译器找不到可以用来表示None的无效值,这时候就必须额外添加标签来区分Some和None。
内存对齐要求
Rust中每个类型都有固定的对齐要求(比如u16需要2字节对齐,u32需要4字节对齐),结构体的对齐要求由其成员中对齐要求最高的那个决定。
如果给Option<u16>用1字节的标签,整个结构体的对齐会被拉低到1字节,违反u16的2字节对齐要求,导致内存访问效率下降甚至出错。因此编译器会将标签的大小扩展到和T的对齐要求匹配:
Option<u16>的标签会被设为2字节,加上u16本身的2字节,总大小4字节,满足对齐要求。Option<u32>的标签设为4字节,加上u32的4字节,总大小8字节。
对于64位系统的u64和u128,标签会使用指针宽度(8字节)来保证对齐和访问效率,这也是为什么Option<u64>总大小是16字节,Option<u128>是24字节。
2. 编译器的具体表示方式
- 可做空值优化的场景:
Option<T>与T大小完全相同,用T的无效位模式标记None。- 比如引用类型
&T:因为Rust中引用永远不会是null,所以Option<&T>直接用null指针表示None,大小和&T一致(64位系统为8字节)。 Option<bool>:用1字节存储,Some(false)=0、Some(true)=1、None=2。
- 比如引用类型
- 不可做空值优化的场景:
Option<T>被表示为包含标签和T值的复合结构,标签大小匹配T的对齐要求:Option<u8>:1字节标签 + 1字节u8,总大小2字节。Option<u16>:2字节标签 + 2字节u16,总大小4字节。Option<u64>:8字节标签 + 8字节u64,总大小16字节。Option<u128>:8字节标签 + 16字节u128,总大小24字节。
内容的提问来源于stack exchange,提问作者Saroupille
相关产品推荐
相关产品推荐

