如何从C数组指针创建一个Vec并在Rust中安全地释放它?

7

我正在使用Rust调用一个需要空指针作为参数的C函数,然后分配一些内存来指向它。那么,如何高效地(即避免不必要的复制)且安全地(即避免内存泄漏或段错误)将C指针中的数据转换为Vec?我的代码示例如下:

extern "C" {
    // C function that allocates an array of floats
    fn allocate_data(data_ptr: *mut *const f32, data_len: *mut i32);
}

fn get_vec() -> Vec<f32> {
    // C will set this to length of array it allocates
    let mut data_len: i32 = 0;

    // C will point this at the array it allocates
    let mut data_ptr: *const f32 = std::ptr::null_mut();

    unsafe { allocate_data(&mut data_ptr, &mut data_len) };

    let data_slice = unsafe { slice::from_raw_parts(data_ptr as *const f32, data_len as usize) };
    data_slice.to_vec()
}

如果我理解正确,.to_vec() 会将数据从切片复制到一个新的 Vec 中,因此底层内存仍需被释放(当切片被丢弃时,底层内存不会被释放)。
对于上述情况,应该采取何种正确方法呢?
  • 我可以创建一个 Vec,它拥有底层内存的所有权,在 Vec 被释放时,底层内存也会被释放吗?
  • 如果不能,那么在 Rust 中应该在哪里/如何释放 C 函数分配的内存?
  • 上述内容中是否还有其他需要改进的地方?
1个回答

17

我能创建一个拥有底层内存所有权的Vec吗?当Vec被释放时,内存也被释放?

不安全的,你绝不能使用Vec::from_raw_parts,除非指针最初来自Vec(或者来自相同的内存分配器)。否则,您将尝试释放您的分配器不知道的内存;非常糟糕。

请注意,对于String::from_raw_parts也是如此,因为StringVec<u8>的包装器。

在Rust中,我应该在哪里/如何释放C函数分配的内存?

只要你用完了就可以。

以上还有什么需要改进的吗?

  • 调用slice::from_raw_parts时无需进行指针转换
  • 变量上不需要显式类型
  • 使用ptr::null而不是ptr::null_mut
  • 执行空指针检查
  • 检查长度是否为非负数
use std::{ptr, slice};

extern "C" {
    fn allocate_data(data_ptr: *mut *const f32, data_len: *mut i32);
    fn deallocate_data(data_ptr: *const f32);
}

fn get_vec() -> Vec<f32> {
    let mut data_ptr = ptr::null();
    let mut data_len = 0;

    unsafe {
        allocate_data(&mut data_ptr, &mut data_len);
        assert!(!data_ptr.is_null());
        assert!(data_len >= 0);

        let v = slice::from_raw_parts(data_ptr, data_len as usize).to_vec();
        deallocate_data(data_ptr);

        v
    }
}

fn main() {}
你没有说明为什么需要它作为Vec,但是如果你永远不需要改变大小,你可以创建自己的类型,该类型可被解引用为片段,并在适当时丢弃数据:
use std::{ptr, slice};

extern "C" {
    fn allocate_data(data_ptr: *mut *const f32, data_len: *mut i32);
    fn deallocate_data(data_ptr: *const f32);
}

struct CVec {
    ptr: *const f32,
    len: usize,
}

impl std::ops::Deref for CVec {
    type Target = [f32];

    fn deref(&self) -> &[f32] {
        unsafe { slice::from_raw_parts(self.ptr, self.len) }
    }
}

impl Drop for CVec {
    fn drop(&mut self) {
        unsafe { deallocate_data(self.ptr) };
    }
}

fn get_vec() -> CVec {
    let mut ptr = ptr::null();
    let mut len = 0;

    unsafe {
        allocate_data(&mut ptr, &mut len);
        assert!(!ptr.is_null());
        assert!(len >= 0);

        CVec {
            ptr,
            len: len as usize,
        }
    }
}

fn main() {}

另请参阅:


非常感谢@Shepmaster,您提供了一些很好的指导。str::ptr是一个打字错误,应该是std::ptr,我会进行修正。关于CVec的优秀想法。有一件事情我仍然不确定 - 我正在封装的API没有为其创建的数组提供释放方法,使用libc::free是否合适(即在impl Drop中使用libc::free(self.ptr))? - Dave Challis
4
使用 libc::free 是否合适呢?不太确定。这个函数 可能 能用;但在从C语言使用该函数时,您需要调用什么来释放数据?那才是正确的做法。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接