为什么这些未对齐的指针引用有效?

Goo*_*opy -1 casting alignment rust

在 Rust 中,将 a 转换*const u8为 a*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 ]
Run Code Online (Sandbox Code Playgroud)

这是我运行的代码

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

代码输出:

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
Run Code Online (Sandbox Code Playgroud)

问题是为什么这段代码永远不会断言,即使它 [1] 不安全地取消引用未对齐的指针或 [2] 调用显式需要有效对齐的 ptr::read() ?

Cha*_*man 5

取消引用未对齐的指针是未定义的行为。未定义 行为是未定义的,任何事情都可能发生,其中包括预期的结果。这并不意味着代码是正确的。具体来说,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);
   |                 ^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

  • @GoodArtistsCopy 没有必要悲观任何事情。X86 架构允许读取未对齐的值,因此为读取任何地址而编写的任何代码都将产生预期的结果。其他架构可能不允许这样做,并且可能会产生一些其他结果。这就是为什么有“read_unaligned”在任何地方都适用的原因。 (2认同)
  • @SamiKuhmonen 你说的是正确的,我只是想强调 **这段代码在任何地方都是不正确的,即使在 x86 上也是如此。您正在为 Rust 抽象机而不是 x86 进行编译,并且它不允许未对齐的读取。 (2认同)