Unaligned Memory in Rust - Forcing Odd Addresses and Loading Data Safely
Rust provides strong guarantees about memory safety, alignment, and the behavior of references. However, when you step into the world of low‑level programming — manual allocation, pointer arithmetic, and raw memory manipulation — it becomes your job to uphold these guarantees. One interesting challenge: Can we intentionally place a u32 at an odd address and still read it safely?
In this article, we explore exactly that. We allocate raw memory, force an odd address, store a struct at that location, and then safely load an unaligned 32‑bit value.
Code Snippet
```Rust use std::alloc::{alloc, Layout}; use std::ptr::addr_of;#[repr(C, align(1))] struct Aligned1 { b: u32, }
fn main() {
// 1) Allocate raw memory — at least sizeof(struct)+1 bytes
//
// We add +1 byte so we can later shift the pointer by 1,
// ensuring the resulting address is guaranteed to be odd.
let layout = Layout::from_size_align(std::mem::size_of::<Aligned1>() + 1, 1).unwrap();
let raw = unsafe { alloc(layout) };
// 2) Make the address ODD by shifting it by 1 byte
//
// Raw allocation may return an even or odd address (usually even).
// Adding 1 forces the resulting pointer to have the lowest bit = 1.
let odd_ptr = unsafe { raw.add(1) };
// 3) Store the struct at the odd address
//
// This is an *unaligned write* — allowed because we are writing raw bytes,
// not creating a reference to unaligned data.
unsafe { odd_ptr.cast::<Aligned1>().write(Aligned1 { b: 0x11223344 }); }
let p: *const Aligned1 = odd_ptr.cast();
// 4) Obtain the address of field `b`
//
// addr_of! does NOT create a reference (&T), so it is safe even when unaligned.
let ptr_b = unsafe { addr_of!((*p).b) };
println!("addr(struct) = {:p}", p);
println!("addr(b) = {:p}", ptr_b);
println!("addr(b) % 2 = {}", (ptr_b as usize) % 2); // should be 1
unsafe {
// Direct dereference of *ptr_b WOULD CRASH:
//
// panic: misaligned pointer dereference
//
// because loading a `u32` from a non‑4‑byte-aligned address is illegal
// on most CPUs.
// Uncomment to get crash:
// println!("b via raw = 0x{:X}", *ptr_b);
}
// -------------------------------------------------------------------------
// Crash-free version
// -------------------------------------------------------------------------
// 1) Allocate raw memory again (same method)
let layout = Layout::from_size_align(std::mem::size_of::<Aligned1>() + 1, 1).unwrap();
let raw = unsafe { alloc(layout) };
// 2) Shift by 1 byte → guaranteed ODD address
let odd_ptr = unsafe { raw.add(1) };
// 3) Unaligned store
unsafe { odd_ptr.cast::<Aligned1>().write(Aligned1 { b: 0x11223344 }); }
// 4) Pointer to the struct and pointer to the field
let p = odd_ptr.cast::<Aligned1>();
let ptr_b = unsafe { addr_of!((*p).b) };
println!("addr(b) = {:p}", ptr_b);
// ✅ 5) Read the 4 bytes manually into a byte array.
//
// This avoids any alignment requirements because reading `u8` is always legal.
let bytes: [u8; 4] = unsafe {
[
*ptr_b.cast::<u8>().add(0),
*ptr_b.cast::<u8>().add(1),
*ptr_b.cast::<u8>().add(2),
*ptr_b.cast::<u8>().add(3),
]
};
// ✅ 6) Reassemble the u32 from bytes without invoking undefined behavior.
//
// This is the correct and portable way to load unaligned integers.
let value = u32::from_ne_bytes(bytes);
println!("value = 0x{:X}", value);
}
</details>