点击上方蓝字 江湖评谈设为关注/星标
前言
Rust内存安全,到底是如何确保安全的呢?堆上的数据,对于托管语言比如C# ,它是由GC来对堆上的数据进行回收。如果在Rust堆上分配数据,它的堆是如何回收的呢?本篇看下Rustc智能指针Box::new的过程。
原理
例子:
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}
这里的运行过程:通过智能指针Box::new分配了一个堆指针,然后把数值5存储到这个堆指针里面。当前的main函数运行完成的时候,释放掉堆空间。
运行原理呢,Box::new会调用exchange_malloc函数分配一个堆指针,exchange_malloc的原型如下:
//https://github.com/rust-lang/rust/blob/1.82.0/library/alloc/src/alloc.rs
/// The allocator for unique pointers.
#[cfg(all(not(no_global_oom_handling), not(test)))]
#[lang = "exchange_malloc"]
#[inline]
unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 {
let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
match Global.allocate(layout) {
Ok(ptr) => ptr.as_mut_ptr(),
Err(_) => handle_alloc_error(layout),
}
}
把这个堆指针返回,然后在堆指针指向的地方赋值为5。这里用的是寄存器保存了堆指针,进行了地址赋值:
mov dword ptr [rax], 0x5
最后当main函数返回之前的指令ret处调用drop_in_place函数释放掉堆空间。drop_in_place函数原型:
//https://github.com/rust-lang/rust/blob/1.82.0/library/core/src/ptr/mod.rs#l574
pub unsafe fn drop_in_place<T: ?Sized>(to_drop: *mut T) {
// Code here does not matter - this is replaced by the
// real drop glue by the compiler.
// SAFETY: see comment above
unsafe { drop_in_place(to_drop) }
}
它这里强调的是一个作用域,在main函数的作用域之内堆指针是有效的,离开了就立刻释放掉。
底层
为了验证下以上说法,这里动用下lldb。通过cargo build编译下以上例子,在target/debug目录下生成了带有调试符号的二进制。此时附加下lldb。在main下端,会直接运行到例子main入口处。
我们很清楚的看到了Box::new调用了exchange_malloc,同样的在main作用域快要结束(也就是main的结尾处),我们看到了drop_in_place调用:
结尾
总体上来说,rust堆空间操作分为三部分。其一:堆空间申请。其二:堆空间赋值修改。其三:堆空间的释放。一般的对于托管语言,堆空间的申请和释放通过GC来进行。Rust智能指针通过作用域的判断,来申请和释放当前堆的空间。从而避免了自带一个GC垃圾回收器这种较为沉重的设计而进行的操作。
往期精彩回顾