In Rust, turning a *const u8 into a *const T is ok, but dereferencing the cast pointer is unsafe because the memory pointed to may not satisfy T‘s requirements of size, alignment and valid byte pattern. I’m trying to come up with an example that violates the alignment requirement, but satisfy the 2 others.
So I generate a random slice of 7 u8 and try to interpret different length-4 sub-slices as an f32 value. Any byte patttern is a valid f32 and 4 u8 are indead size_of::<f32>(). So the only thing that varies is the alignment of the sub-slice pointer, which is shifted from the base slice:
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 ]
This is the code that I run
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);
}
}
The code outputs:
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
The question is why is this code never asserting, even though it [1] unsafely dereference an unaligned pointer or [2] calls ptr::read() that explicitly requires valid alignment ?
>Solution :
Dereferencing an unaligned pointer is Undefined Behavior. Undefined Behavior is undefined, anything can happen, and that includes the expected result. It does not mean the code is correct. Specifically, x86 allows unaligned read, so this is likely the reason it does not fail.
Miri indeed reports an error in your code:
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);
| ^^^^^^^^^^^