mcm*_*yer 1 heap-memory ffi rust
这个问题并不新鲜,据我所知,有两种方法:
Vec<T>按照此处的建议使用我的问题是这是否确实是两个(好的)选择。
只是为了清楚地表明这一点:两种方法都有效。问题是是否还有另一种可能是首选的方式。下面的示例旨在确定 Vec 的使用效果不好,以及其他方法可能更好的地方。
让我们陈述一下问题:假设有一个 C 库需要写入一些缓冲区。例如,这可能是一个压缩库。最简单的方法是让 Rust 分配堆内存并对其进行管理,而不是在 C/C++ 中使用malloc/进行分配new,然后以某种方式将所有权传递给 Rust。
让我们以压缩为例。如果库允许增量(流)压缩,那么我需要一个缓冲区来跟踪某些偏移量。
按照方法 1(即:“滥用” Vec<T>),我将包装Vec并使用lenandcapacity来达到我的目的:
/// `Buffer` is basically a Vec
pub struct Buffer<T>(Vec<T>);
impl<T> Buffer<T> {
/// Create new buffer of length `len`
pub fn new(len: usize) -> Self {
Buffer(Vec::with_capacity(len))
}
/// Return length of `Buffer`
pub fn len(&self) -> usize {
return self.0.len()
}
/// Return total allocated size of `Buffer`
pub fn capacity(&self) -> usize {
return self.0.capacity()
}
/// Return remaining length of `Buffer`
pub fn remaining(&self) -> usize {
return self.0.capacity() - self.len()
}
/// Increment the offset
pub fn increment(&mut self, by:usize) {
unsafe { self.0.set_len(self.0.len()+by); }
}
/// Returns an unsafe mutable pointer to the buffer
pub fn as_mut_ptr(&mut self) -> *mut T {
unsafe { self.0.as_mut_ptr().add(self.0.len()) }
}
/// Returns ref to `Vec<T>` inside `Buffer`
pub fn as_vec(&self) -> &Vec<T> {
&self.0
}
}
Run Code Online (Sandbox Code Playgroud)
唯一有趣的函数是increment和as_mut_ptr。
Buffer会像这样使用
fn main() {
// allocate buffer for compressed data
let mut buf: Buffer<u8> = Buffer::new(1024);
loop {
// perform C function call
let compressed_len: usize = compress(some_input, buf.as_mut_ptr(), buf.remaining());
// increment
buf.increment(compressed_len);
}
// get Vec inside buf
let compressed_data = buf.as_vec();
}
Run Code Online (Sandbox Code Playgroud)
Buffer<T>此处所示显然是危险的,例如,如果使用任何引用类型。即使T=bool也可能导致未定义的行为。但是,可以通过引入限制可能类型的特征来避免未初始化实例的T问题T。
另外,如果对齐很重要,那么这Buffer<T>不是一个好主意。
但除此之外,这Buffer<T>真的是最好的方法吗?
似乎没有现成的解决方案。bytes crate 很接近,它提供了一个“用于在连续内存片上存储和操作的容器”,但接口不够灵活。
您完全可以使用aVec的剩余容量来手动写入。这就是为什么.set_len()可用。但是,compress()必须知道给定的指针指向未初始化的内存,因此不允许从中读取(除非首先写入),并且必须保证返回的长度是初始化的字节数。我认为这些规则在 Rust 和 C 或 C++ 之间在这方面大致相同。
用 Rust 写成这样:
pub struct Buffer<T>(Vec<T>);
impl<T> Buffer<T> {
pub fn new(len: usize) -> Self {
Buffer(Vec::with_capacity(len))
}
/// SAFETY: `by` must be less than or equal to `space_len()` and the bytes at
/// `space_ptr_mut()` to `space_ptr_mut() + by` must be initialized
pub unsafe fn increment(&mut self, by: usize) {
self.0.set_len(self.0.len() + by);
}
pub fn space_len(&self) -> usize {
self.0.capacity() - self.0.len()
}
pub fn space_ptr_mut(&mut self) -> *mut T {
unsafe { self.0.as_mut_ptr().add(self.0.len()) }
}
pub fn as_vec(&self) -> &Vec<T> {
&self.0
}
}
unsafe fn compress(_input: i32, ptr: *mut u8, len: usize) -> usize {
// right now just writes 5 bytes if there's space for them
let written = usize::min(5, len);
for i in 0..written {
ptr.add(i).write(0);
}
written
}
fn main() {
let mut buf: Buffer<u8> = Buffer::new(1024);
let some_input = 5i32;
unsafe {
let compressed_len: usize = compress(some_input, buf.space_ptr_mut(), buf.space_len());
buf.increment(compressed_len);
}
let compressed_data = buf.as_vec();
println!("{:?}", compressed_data);
}
Run Code Online (Sandbox Code Playgroud)
你可以在操场上看到它。如果你通过 Miri 运行它,你会发现它没有发现任何未定义的行为,但如果你过度宣传你已经写入了多少内容(比如 return written + 10),那么它确实会产生一个错误,即检测到读取未初始化的内存。
没有开箱即用的类型的原因之一是因为Vec该类型:
fn main() {
let mut buf: Vec<u8> = Vec::with_capacity(1024);
let some_input = 5i32;
let spare_capacity = buf.spare_capacity_mut();
unsafe {
let compressed_len: usize = compress(
some_input,
spare_capacity.as_mut_ptr().cast(),
spare_capacity.len(),
);
buf.set_len(buf.len() + compressed_len);
}
println!("{:?}", buf);
}
Run Code Online (Sandbox Code Playgroud)
您的Buffer类型并没有真正增加任何便利性或安全性,第三方板条箱也无法这样做,因为它依赖于compress().
这样的 Buffer 真的是最好的方法吗?
是的,这几乎是提供写入缓冲区的成本最低的方法。查看生成的发布程序集,它只是一次分配调用,仅此而已。如果您多次执行此操作,则使用特殊的分配器或简单地预分配并重用分配可能会变得棘手(但请务必进行测量,因为内置分配器无论如何都会执行此操作,只是更一般而言)。
| 归档时间: |
|
| 查看次数: |
479 次 |
| 最近记录: |