为何Rust中transmute将字节数组转元组时会重排字节?
为什么transmute转换后变量取值不符合预期?
示例代码
fn main() { let buffer: [u8; 16] = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f ]; let (a, b, c, d, e): (u8, u8, u8, [u8; 4], [u8; 9]) = unsafe { core::mem::transmute(buffer) }; println!("{a:#x}"); println!("{b:#x}"); println!("{c:#x}"); println!("{d:#x?}"); println!("{e:#x?}"); }
运行输出
0x4 0x5 0x6 [ 0x0, 0x1, 0x2, 0x3, ] [ 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, ]
问题
为何变量a、b、c未分别获取0x00、0x01、0x02的值,反而这些值被存入了变量d中?
原因及解决方案
这是因为Rust中默认的元组(以及未指定布局的结构体)采用repr(Rust)内存布局,编译器有权自由调整元素的内存排列顺序——哪怕元素的大小和对齐不会产生内存浪费,编译器也可能为了优化做调整。
你通过unsafe { core::mem::transmute }直接将数组的二进制内存转换为元组类型,本质上依赖了元组元素的内存顺序,但这个顺序是未定义的,没有语言层面的保证,所以才会出现和预期不符的取值结果。
方案1:使用安全的拆分方法
如果需要按固定顺序拆分数组,推荐用安全的方式,比如数组索引或者split_array_ref:
fn main() { let buffer: [u8; 16] = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]; let (head, rest) = buffer.split_array_ref::<3>(); let (d, e) = rest.split_array_ref::<4>(); let [a, b, c] = head; println!("{a:#x}"); println!("{b:#x}"); println!("{c:#x}"); println!("{d:#x?}"); println!("{e:#x?}"); }
方案2:使用固定布局的类型(若必须用transmute)
如果一定要用transmute,必须使用具有固定内存布局的类型,比如用#[repr(C)]修饰的结构体(repr(C)会严格按照声明顺序排列元素):
#[repr(C)] struct SplitBuffer { a: u8, b: u8, c: u8, d: [u8; 4], e: [u8; 9], } fn main() { let buffer: [u8; 16] = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]; let split = unsafe { core::mem::transmute::<[u8; 16], SplitBuffer>(buffer) }; println!("{split.a:#x}"); println!("{split.b:#x}"); println!("{split.c:#x}"); println!("{split.d:#x?}"); println!("{split.e:#x?}"); }
内容的提问来源于stack exchange,提问作者Matt Thomas
相关产品推荐
相关产品推荐

