Создание Vec в Rust из указателя массива C и его безопасное освобождение? - PullRequest
0 голосов
/ 16 мая 2018

Я вызываю функцию C из Rust, которая принимает нулевой указатель в качестве аргумента, а затем выделяет некоторую память для ее указания.

Как правильно (т.е. избежать ненужных копий) и безопасно (т.е. избежать утечек памяти или ошибок) преобразовать данные из указателя 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 Ответ

0 голосов
/ 16 мая 2018

Могу ли я создать Vec, который становится владельцем базовой памяти, которая освобождается при освобождении Vec?

Не безопасно, нет.Вы не должны использовать Vec::from_raw_parts, если указатель не исходил из Vec изначально (ну, из того же распределителя памяти).В противном случае вы попытаетесь освободить память, о которой ваш распределитель не знает;очень плохая идея.

где / как в Rust я должен освободить память, выделенную функцией C?

Как только вы закончите с этим и не раньше.

что-нибудь из вышеперечисленного, что можно / нужно улучшить?

  • Нет необходимости приводить указатель при вызове slice::from_raw_parts
  • Нет необходимости явно указывать типы переменных
  • Использовать ptr::null, а не ptr::null_mut
  • Выполнить проверку указателя NULL
  • Проверить, что длина неотрицательный
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() {}

См. также:

...