I'm trying to allocate a struct containing Vec with the global allocator. (To simplify this problem, use Vec here.) It seems that an error may occur when allocating Vec using the std::alloc::alloc function.
How should I fix it in Unsafe Rust?
#[test]
fn allocation() {
let layout = std::alloc::Layout::new::<Vec<u32>>();
let ptr = unsafe{ std::alloc::alloc(layout) } as *mut Vec<u32>;
dbg!(ptr);
let ptr_ref = unsafe{ &mut *ptr as &mut Vec<u32> };
ptr_ref.resize_with(16, || {
dbg!("initializing");
0
});
dbg!(&ptr_ref[0] as *const u32);
unsafe{ std::alloc::dealloc( ptr as *mut u8, layout); }
}
When run the above code, it sometimes ends with "0xc0000005, STATUS_ACCESS_VIOLATION" or "0xc0000374, STATUS_HEAP_CORRUPTION". Also, when these errors occur, it seems that initialization (closure that returns the initial value) has not been executed.
Edit:
It seems that my code didn't include the initialization of Vec. JMAA's answer gives details. So I added the following code before casting to &mut Vec<u32>
.
unsafe {
std::ptr::write(ptr, Vec::with_capacity(16));
}
The details of the std::ptr::write
function are here.
This seems to work fine.
CodePudding user response:
Right, so I think there are a couple of different issues to unpack here.
First you need to understand what a Vec
is. A Vec
is not like a C-style array, but rather more like a C std::vector
. The Vec
object itself just contains two items (often called the "control block"): a wide pointer to the heap-allocated space for the elements of the Vec
(including the allocated capacity), and the current length of the Vec
. The actual elements of the Vec
exist elsewhere, in memory allocated by the Vec
itself and pointed to by that wide pointer it holds in the control block.
So, in you code ptr
points to a region of heap memory allocated with enough space to hold a Vec
object, that is to hold the control block. You never actually initialise a Vec
object, just allocate enough memory to hold one.
Then you do your first "bad thing", you cast ptr
to a &mut Vec
. But you never initialised a Vec
object there (i.e., the allocated memory for the control block has not been initialised). References in Rust cannot point to uninitialised memory, doing violates safety and your whole program is invalid. In particular, you call the resize_with
method on a "Vec
", but there is no Vec
there, just some block of heap memory with the size and alignment to theoretically hold a Vec
's control block.
What I suppose you think you're doing is that the pointer to Vec
doesn't point at the control block, but directly to the heap memory containing the elements of the Vec
. This would make some sense of why you try to dealloc
the way that you do, with a pointer to the first element of the Vec
rather than the Vec
itself. That is also wrong, since that isn't pointing at the memory you alloc
'd.
So what should you be doing? I'm not an expert because I've never felt the need to manually de/allocate in Rust before, but my understanding is that you can simply do *ptr = Vec::new()
(or whatever Vec
constructor you want) and then you can cast back to a &mut Vec
since there's now an initialised Vec
there.
Then, when you're done you fist need to make sure that the Vec
gets dropped -- this will cause it to internally deallocate the space allocated for the elements -- before you do dealloc(ptr.cast(), layout)
.