问题的背景:
Rust 最初设计为一种优先考虑内存安全的语言。然而,在某些任务中,例如处理 FFI 或低级分配器时,必须使用原始指针并手动管理动态内存。这类任务在系统编程和性能优化中都会遇到。因此,了解 Rust 如何防止内存泄漏、悬空指针和使用后释放(use-after-free)非常重要。
问题:
原始指针(*const T, *mut T)未集成到 Rust 的所有权与借用检查系统中:它们可能指向无效内存,被错误释放或根本不被释放。处理这些指针的错误可能导致未定义行为(UB)、崩溃、安全漏洞或内存泄漏。
解决方案:
建议使用安全类型替代原始指针——Box、Rc、Arc,而对于临时借用——使用借用引用。如果确实不可避免地需要使用原始指针(例如,处理 C API),则将所有操作封装在 unsafe 块中,精心组织 Drop,并尽可能使用如 NonNull 之类的 crate。另一种技术是 RAII 封装和最小化指针的生命周期。
代码示例:
fn allocate_in_heap() -> Box<i32> { Box::new(100) } // 内存将自动释放。 // 使用原始指针 unsafe fn leak_memory() { let ptr = libc::malloc(4) as *mut i32; if !ptr.is_null() { *ptr = 42; // libc::free(ptr); // 如果忘记释放——内存泄漏! } }
关键特性:
Box 在删除时是否保证自动清理所有嵌套值?
是的,删除 Box<T> 时,析构函数会首先清理外部包装,然后递归清理内部的所有数据(直到 Vec 或结构体 T 内的其他 Box 元素)。
是否可以安全地通过多个函数传递结构的原始指针,而不冒用 use-after-free 的风险?
不可以,原始指针不携带对象的生命周期信息。编译器无法检查安全性,因此所有责任完全在开发者身上:如果对象已被释放,原始指针将指向无效区域。
如果手动使用 free 或 drop_in_place,Rust 会否在同一地址上调用 Drop 两次?
会的,如果在手动释放后保留另一个 Box/指针引用同一块内存,则在销毁第二个实例时会再次调用 Drop,从而引发 UB。永远不要手动释放由 Box、Vec 等管理的内存。
程序员从外部 C 库获取原始指针,在使用后未释放或在释放时间上出了错。
优点:
缺点:
使用具有 Drop 的 RAII 封装,通过 Box 或 NonNull 封装指针,所有内容在作用域结束时安全销毁。
优点:
缺点: