rust 为什么这些不对齐的指针延迟会起作用?

thtygnil  于 2023-02-16  发布在  其他
关注(0)|答案(1)|浏览(83)

在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()?

uidvcgyl

uidvcgyl1#

取消引用未对齐的指针是Undefined Behavior。Undefined Behavior是未定义的,任何事情都可能发生,并且包括预期的结果。这并不意味着代码是正确的。具体来说,x86允许未对齐的读取,所以这可能是它没有失败的原因。
Miri确实在代码中报告了一个错误:

error: Undefined Behavior: accessing memory with alignment 1, but alignment 4 is required
  --> src/main.rs:10:9
   |
10 |         *ptr
   |         ^^^^ accessing memory with alignment 1, but alignment 4 is required
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
   = note: BACKTRACE:
   = note: inside `to_f32` at src/main.rs:10:9: 10:13
note: inside `main`
  --> src/main.rs:28:17
   |
28 |         let f = to_f32(ptr);
   |                 ^^^^^^^^^^^

相关问题