为何用Box分配内存后通过FFI释放不会造成内存泄漏?
哈哈,这个问题我刚搞Rust FFI的时候也纠结过好久!毕竟跨语言搞内存管理,稍不注意就踩泄漏的坑,结果valgrind说啥都没漏,反而有点不适应了对吧?
其实核心原因主要有这两点:
默认分配器的底层兼容性:大多数系统环境下,Rust默认的全局内存分配器和C/C用的系统标准分配器是一套玩意儿——比如Linux上的glibc malloc/free,Windows上的HeapAlloc这类。所以你用Rust的Box在堆上分配内存时,底层调用的和C malloc是同一个分配器,这时候就算你在C++里直接用free释放(不过这种写法其实不推荐哈,分配和释放最好绑定同一种语言的逻辑),内存也能被正确回收,valgrind自然就看不到泄漏痕迹。
标准FFI内存管理逻辑在起作用:估计你写Rust库的时候,肯定也同时暴露了一个专门的释放函数对吧?比如用
Box::into_raw把Box转成裸指针给C用,然后再写个函数,用Box::from_raw把指针转回到Box里——这时候Box的自动析构机制就会生效,帮你把内存乖乖释放掉。只要C那边用完指针后,调用了Rust提供的这个释放函数,那内存就被妥妥回收了,valgrind当然会显示一切正常。
举个简单的代码例子你就懂了:
Rust侧的代码大概是这样:
#[no_mangle] pub extern "C" fn produce_data() -> *mut MyData { // 用Box分配数据,转成裸指针对外暴露 Box::into_raw(Box::new(MyData::default())) } #[no_mangle] pub extern "C" fn free_data(ptr: *mut MyData) { unsafe { // 将裸指针转回Box,销毁时自动释放内存 let _ = Box::from_raw(ptr); } }
然后C++那边调用produce_data拿到指针,用完后调用free_data,整个内存生命周期就闭环了,完全不会有泄漏。
总结一下就是:要么是分配器底层兼容,跨语言释放也能正常工作;要么是你按照标准的FFI写法,让Rust自己来处理分配内存的释放,自然就不会出现内存泄漏啦。
备注:内容来源于stack exchange,提问作者t348575

