1

I saw the following code for returning a byte array to C:

#[repr(C)]
struct Buffer {
    data: *mut u8,
    len: usize,
}

extern "C" fn generate_data() -> Buffer {
    let mut buf = vec![0; 512].into_boxed_slice();
    let data = buf.as_mut_ptr();
    let len = buf.len();
    std::mem::forget(buf);
    Buffer { data, len }
}

extern "C" fn free_buf(buf: Buffer) {
    let s = unsafe { std::slice::from_raw_parts_mut(buf.data, buf.len) };
    let s = s.as_mut_ptr();
    unsafe {
        Box::from_raw(s);
    }
}

I notice that the free_buf function takes a Buffer, instead of a *mut u8. Is this intentional?

Can the free_buf function be reduced to:

unsafe extern "C" fn free_buf(ptr: *mut u8) {
    Box::from_raw(ptr);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Foobar
  • 7,458
  • 16
  • 81
  • 161
  • No. Why do you think that the _amount of data to free_ is unimportant? – Shepmaster May 20 '21 at 20:54
  • 1
    My understanding of memory management is weak, but as far as I'm aware, in C you can call `malloc` (with a parameter for the amount of memory to allocate), which returns a pointer, and then later call `free` on that pointer (without passing in a parameter specifying how much memory to free). – Foobar May 20 '21 at 20:59
  • 1
    malloc API handle the size itself, rust alloc api let the user handle the size information, this is often allow to avoid duplicate information but rely on user more. (but normal user of rust don't normally need to know this) – Stargateur May 20 '21 at 21:03

3 Answers3

1

You are correct to note that the C runtime free function takes only a pointer to the memory region to be freed as an argument.

However, you don't call this directly. In fact Rust has a layer that abstracts away the actual memory allocator being used: std::alloc::GlobalAlloc.

The reason for providing such an abstraction is to allow other allocators to be used, and in fact it is quite easy to swap out the default OS provided allocator.

It would be quite limiting to require that any allocator keeps track of the length of blocks to allow them to be freed without supplying the length to the deallocation function, so the general deallocation function requires the length as well.

You might be interested to know that C++ has a similar abstraction. This answer provides some more discussion about why it could be preferable to require the application to keep track of the lengths of allocated memory regions rather than the heap manager.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
harmic
  • 28,606
  • 5
  • 67
  • 91
1

If we check the type of Box::from_raw, we see that it construct a Box<u8> from a raw *mut u8. One would need a *mut [u8] (fat pointer to slice) in order to construct a Box<[u8]> (which is what we have in the very beginning).

And dropping a Box<u8> will (at best) only release one byte of memory (if not causing a runtime error), while dropping a Box<[u8]> correctly releases all the memory.

Ruifeng Xie
  • 813
  • 4
  • 16
0

No, what you do is undefined behavior, it's mandatory that the type between into_raw() and from_raw() match. Rust alloc API doesn't require the allocator to remember any information, and so the allocation implementation will expect correctness of all information pass to it.

In your example, *mut u8 and *mut [u8] are a totally different type and so have different layout.

Also, mismatch the type could prevent destructor to run properly.

You can't use from_raw() to destruct any pointer like C free() using void *.

Stargateur
  • 24,473
  • 8
  • 65
  • 91