2016-08-31 6 views
6

Я пишу привязки Rust к библиотеке C, которая имеет возможность использовать сторонний распределитель памяти. Его интерфейс выглядит следующим образом:Как использовать распределитель памяти Rust для библиотеки C, которая может быть предоставлена ​​распределителем?

struct allocator { 
    void*(*alloc)(void *old, uint); 
    void(*free)(void*); 
}; 

Соответствующая Rust структура, я предполагаю следующее:

#[repr(C)] 
#[derive(Copy, Clone, Debug, PartialEq)] 
pub struct Allocator { 
    alloc: Option<extern "C" fn(*mut c_void, c_uint) -> *mut c_void>, 
    free: Option<extern "C" fn(*mut c_void)>, 
} 

Как я могу реализовать эти две Экстерн функции, которые должны имитировать аллокатор? Я не нашел ничего похожего на API-интерфейс распределителя в Rust (я понимаю, почему же), поэтому мне интересно, если это возможно.

+0

https://www.reddit.com/r/rust/comments/2eqdg2/allocate_a_vec_on_cs_heap/ Есть несколько мыслей по подобной теме, хотя управление жизненным циклом распределения может быть сложным. – snuk182

+1

Как вы отмечаете, эта тема касается того, что Rust использует тот же распределитель, что и C. Это возможно с помощью [настраиваемых распределителей] (https://doc.rust-lang.org/stable/book/custom-allocators.html). – Shepmaster

ответ

6

Это не так просто, как вам может понравиться.

Способы распределения раскрываются в heap module of the alloc crate.

Создание некоторых методов оболочки и заполнение структуры является прямым вперед, но мы быстро столкнулись с проблемой:

#![feature(heap_api)] 

extern crate libc; 
extern crate alloc; 

use libc::{c_void, c_uint}; 
use alloc::heap; 

#[repr(C)] 
#[derive(Copy, Clone, Debug, PartialEq)] 
pub struct Allocator { 
    alloc: Option<extern "C" fn(*mut c_void, c_uint) -> *mut c_void>, 
    free: Option<extern "C" fn(*mut c_void)>, 
} 


extern "C" fn alloc_ext(old: *mut c_void, size: c_uint) -> *mut c_void { 
    if old.is_null() { 
     heap::allocate(size as usize, align) as *mut c_void 
    } else { 
     heap::reallocate(old as *mut u8, old_size, size as usize, align) as *mut c_void 
    } 
} 

extern "C" fn free_ext(old: *mut c_void) { 
    heap::deallocate(old as *mut u8, old_size, align); 
} 

fn main() { 
    Allocator { 
     alloc: Some(alloc_ext), 
     free: Some(free_ext), 
    }; 
} 

Распределитель Rust рассчитывает быть сказанного размером любого предыдущего распределения, а также желаемые выравнивание. Соответствующий API не имеет никакого способа передать это.

Выравнивание должно (Я не эксперт), чтобы быть в порядке с жестким кодом при некотором значении, скажем, 16 байт. Размер сложнее. Вам, скорее всего, придется украсть некоторые старые трюки C и выделить немного лишнего места для хранения размера. Затем вы можете сохранить размер и вернуть указатель чуть выше этого.

полностью непроверенной пример:

#![feature(alloc, heap_api)] 

extern crate libc; 
extern crate alloc; 

use libc::{c_void, c_uint}; 
use alloc::heap; 
use std::{mem, ptr}; 

#[repr(C)] 
#[derive(Copy, Clone, Debug, PartialEq)] 
pub struct Allocator { 
    alloc: Option<extern "C" fn(*mut c_void, c_uint) -> *mut c_void>, 
    free: Option<extern "C" fn(*mut c_void)>, 
} 

const ALIGNMENT: usize = 16; 

extern "C" fn alloc_ext(old: *mut c_void, size: c_uint) -> *mut c_void { 
    unsafe { 
     // Should check for integer overflow 
     let size_size = mem::size_of::<usize>(); 
     let size = size as usize + size_size; 

     let memory = if old.is_null() { 
      heap::allocate(size, ALIGNMENT) 
     } else { 
      let old = old as *mut u8; 
      let old = old.offset(-(size_size as isize)); 
      let old_size = *(old as *const usize); 
      heap::reallocate(old, old_size, size, ALIGNMENT) 
     }; 

     *(memory as *mut usize) = size; 
     memory.offset(size_size as isize) as *mut c_void 
    } 
} 

extern "C" fn free_ext(old: *mut c_void) { 
    if old.is_null() { return } 

    unsafe { 
     let size_size = mem::size_of::<usize>(); 

     let old = old as *mut u8; 
     let old = old.offset(-(size_size as isize)); 
     let old_size = *(old as *const usize); 

     heap::deallocate(old as *mut u8, old_size, ALIGNMENT); 
    } 
} 

fn main() { 
    Allocator { 
     alloc: Some(alloc_ext), 
     free: Some(free_ext), 
    }; 

    let pointer = alloc_ext(ptr::null_mut(), 54); 
    let pointer = alloc_ext(pointer, 105); 
    free_ext(pointer); 
} 

Не [... using Vec as an allocator ...] более высокого уровня решения?

Это, безусловно, возможно, но я не совсем уверен, как это будет работать с перераспределением. Вам также необходимо будет отслеживать размер и емкость Vec, чтобы восстановить его, чтобы перераспределить его.

+2

В принципе вы также можете сохранить размеры размещения в 'HashMap' или аналогичном. Я не уверен, что вы выиграли бы что-нибудь по сравнению с заселением и хранением в распределении, хотя (за исключением более высокой вероятности обнаружения плохой «свободной» со стороны C). –

+2

@ChrisEmerson хорошая вещь! Однако, когда я пробовал подобные вещи в прошлом, я испытал ненормально низкую производительность. Не зная точно, почему, я думаю, это связано с плохой локальностью кэша. Несколько потоков также более болезненны для совместной коллекции. – Shepmaster

+1

Протестировано для однопоточной среды, нет проблем. – snuk182

 Смежные вопросы

  • Нет связанных вопросов^_^