在Rust中,将*const u8
转换为*const T
是可以的,但取消引用强制转换指针是不安全的,因为所指向的内存可能不满足T
的大小、对齐和有效字节模式的要求。我试图给出一个例子,它违反了对齐要求,但满足了另外两个要求。
因此,我生成了一个7 u8
的随机切片,并尝试将不同长度(4)的子切片解释为f32值。任何字节模式都是有效的f32,4 u8是无效的size_of::<f32>()
。因此,唯一不同的是子切片指针的对齐方式,它从基本切片移位:
slice: [ 0 | 1 | 2 | 3 | 4 | 5 | 6 ]
sub-slices: [ 0 1 2 3 ]
[ 1 2 3 4 ]
[ 2 3 4 5 ]
[ 3 4 5 6 ]
这是我运行的代码
use std::mem::transmute;
use std::ptr::read;
use std::convert::TryInto;
//use rand::Rng;
fn to_f32(v: &[u8]) -> f32 {
let ptr = v.as_ptr() as *const f32;
unsafe {
// [1] dereference
*ptr
// [2] alternatively
//ptr.read()
}
}
fn main() {
println!("align_of::<f32>() = {}", std::mem::align_of::<f32>());
//let mut rng = rand::thread_rng();
// with a pointer on the stack
let v: [u8; 7] = [ 0x4A, 0x3A, 0x2a, 0x10, 0x0F, 0xD2, 0x37];
// with a pointer on the heap
//let v = Box::new(rng.gen::<[u8;7]>());
for i in 0..4 {
let ptr = &v[i..(i+4)];
let f = to_f32(ptr);
// max alignment of ptr
let alignment = 1 << (ptr.as_ptr() as usize).trailing_zeros();
// other ways to convert, as a control check
let repr = ptr.try_into().expect("");
let f2 = unsafe { transmute::<[u8; 4], f32>(repr) };
let f3 = f32::from_le_bytes(repr);
println!("{:x?} [{alignment}]: {repr:02x?} : {f} =? {f2} = {f3}", ptr.as_ptr());
assert_eq!(f, f2);
assert_eq!(f, f3);
}
}
代码输出:
align_of::<f32>() = 4
0x7fffa431a5d1 [1]: [4a, 3a, 2a, 10] : 0.000000000000000000000000000033571493 =? 0.000000000000000000000000000033571493 = 0.000000000000000000000000000033571493
0x7fffa431a5d2 [2]: [3a, 2a, 10, 0f] : 0.000000000000000000000000000007107881 =? 0.000000000000000000000000000007107881 = 0.000000000000000000000000000007107881
0x7fffa431a5d3 [1]: [2a, 10, 0f, d2] : -153612880000 =? -153612880000 = -153612880000
0x7fffa431a5d4 [4]: [10, 0f, d2, 37] : 0.000025040965 =? 0.000025040965 = 0.000025040965
问题是为什么这段代码从不Assert,即使它[1]不安全地解引用了一个未对齐的指针,或者[2]调用了显式要求有效对齐的ptr::read()?
1条答案
按热度按时间uidvcgyl1#
取消引用未对齐的指针是Undefined Behavior。Undefined Behavior是未定义的,任何事情都可能发生,并且包括预期的结果。这并不意味着代码是正确的。具体来说,x86允许未对齐的读取,所以这可能是它没有失败的原因。
Miri确实在代码中报告了一个错误: