在不重新分配的情况下将 Vec 转换为 FFI 的正确方法是什么?

What is the correct way to convert a Vec for FFI without reallocation?

我需要在 FFI 中传递 Vec 个元素。通过实验,我发现了一些有趣的点。我开始为 FFI 提供所有 3 个:ptrlencapacity,以便我可以重建 Vec 以便稍后销毁它:

let ptr = vec.as_mut_ptr();
let len = vec.len();
let cap = vec.capacity();
mem::forget(vec);
extern_fn(ptr, len, cap);

// ...

pub unsafe extern "C" fn free(ptr: *mut u8, len: usize, cap: usize) {
    let _ = Vec::from_raw_parts(ptr, len, cap);
}

我想摆脱 capacity 因为它对我的前端没用;这只是为了让我可以重建我的向量以释放内存。

Vec::shrink_to_fit() 很诱人,因为它似乎消除了处理容量的需要。不幸的是,关于它的文档并不能保证它会 len == capacity,因此我假设在 from_raw_parts() 期间可能会触发未定义的行为。

into_boxed_slice() 似乎可以保证它会从 docs 生成 len == capacity,所以接下来我使用了它。 如有错误请指正。问题是它似乎不能保证不重新分配。这是一个简单的程序:

fn main() {
    let mut v = Vec::with_capacity(1000);
    v.push(100u8);
    v.push(110);
    let ptr_1 = v.as_mut_ptr();
    let mut boxed_slice = v.into_boxed_slice();
    let ptr_2 = boxed_slice.as_mut_ptr();
    let ptr_3 = Box::into_raw(boxed_slice);
    println!("{:?}. {:?}. {:?}", ptr_1, ptr_2, ptr_3);
}

In the playground,它打印:

rustc 1.14.0 (e8a012324 2016-12-16)
0x7fdc9841b000. 0x7fdc98414018. 0x7fdc98414018

如果它必须找到新的内存而不是能够在不导致复制的情况下摆脱额外的容量,这就不好了。

是否有任何其他方法可以将我的向量传递到 FFI(到 C)而不传递容量?看来into_boxed_slice()是我需要的,但是为什么会涉及到重新分配和复制数据呢?

原因比较简单

现代内存分配器将在 "sized" 个板中分离分配,其中每个板负责处理给定的大小范围。例如:

  • 8 字节 slab:1 到 8 字节之间的任何内容
  • 16 字节 slab:9 到 16 字节之间的任何内容
  • 24 字节 slab:17 到 24 字节之间的任何内容
  • ...

当你分配内存时,你要求一个给定的大小,分配器找到正确的 slab,从中获取一个块,然后 returns 你的指针。

当你释放内存时...你希望分配器如何找到正确的 slab?有2个解决方案:

  • 分配器有办法搜索包含您的内存范围的 slab,不知何故,这涉及通过 slab 的线性搜索或某种全局 look-up table 或 . ..
  • 告诉分配器分配块的大小

这里很明显,C 接口(freerealloc)相当 sub-par,因此 Rust 希望使用更高效的接口,即责任所在的接口在来电上。


所以,你有两个选择:

  1. 传递容量
  2. 确保长度和容量相等

如您所知,(2) 可能需要 new 分配,这是非常不受欢迎的。 (1) 可以通过全程传递容量来实现,也可以将其存储在某个点,然后在需要时检索它。

就是这样。你必须评估你的 trade-offs.