Home > Blockchain >  How can I fix STATUS_ACCESS_VIOLATION by Vec<T> allocation?
How can I fix STATUS_ACCESS_VIOLATION by Vec<T> allocation?

Time:04-18

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).

  • Related