揭開智能指針 Box 的神秘面紗
本文轉(zhuǎn)載自微信公眾號「董澤潤的技術(shù)筆記」,作者董澤潤。轉(zhuǎn)載本文請聯(lián)系董澤潤的技術(shù)筆記公眾號。
熟悉 c++ 的肯定知道 shared_ptr, unique_ptr, 而 Rust 也有智能指針 Box, Rc, Arc, RefCell 等等,本文分享 Box 底層實現(xiàn)
Box
入門例子
例子來自 the rust book, 為了演示方便,去掉打印語句
- fn main() {
 - let _ = Box::new(0x11223344);
 - }
 
將變量 0x11223344 分配在堆上,所謂的裝箱,java 同學肯定很熟悉。讓我們掛載 docker, 使用 rust-gdb 查看匯編實現(xiàn)
- Dump of assembler code for function hello_cargo::main:
 - 0x000055555555bdb0 <+0>: sub $0x18,%rsp
 - 0x000055555555bdb4 <+4>: movl $0x11223344,0x14(%rsp)
 - => 0x000055555555bdbc <+12>: mov $0x4,%esi
 - 0x000055555555bdc1 <+17>: mov %rsi,%rdi
 - 0x000055555555bdc4 <+20>: callq 0x55555555b5b0 <alloc::alloc::exchange_malloc>
 - 0x000055555555bdc9 <+25>: mov %rax,%rcx
 - 0x000055555555bdcc <+28>: mov %rcx,%rax
 - 0x000055555555bdcf <+31>: movl $0x11223344,(%rcx)
 - 0x000055555555bdd5 <+37>: mov %rax,0x8(%rsp)
 - 0x000055555555bdda <+42>: lea 0x8(%rsp),%rdi
 - 0x000055555555bddf <+47>: callq 0x55555555bd20 <core::ptr::drop_in_place<alloc::boxed::Box<i32>>>
 - 0x000055555555bde4 <+52>: add $0x18,%rsp
 - 0x000055555555bde8 <+56>: retq
 - End of assembler dump.
 
關鍵點就兩條,alloc::alloc::exchange_malloc 在堆上分配內(nèi)存空間,然后將 0x11223344 存儲到這個 malloc 的地址上
函數(shù)結(jié)束時,將地址傳遞給 core::ptr::drop_in_place 去釋放,因為編譯器知道類型是 alloc::boxed::Box
單純的看這個例子,Box 并不神秘,對應匯編實現(xiàn),和普通指針沒區(qū)別,一切約束都是編譯期行為
所有權(quán)
- fn main() {
 - let x = Box::new(String::from("Rust"));
 - let y = *x;
 - println!("x is {}", x);
 - }
 
這個例子中將字符串裝箱,其實沒必要這么寫,因為 String 廣義來講本身就是一種智能指針。這個例子會報錯
- 3 | let y = *x;
 - | -- value moved here
 - 4 | println!("x is {}", x);
 - | ^ value borrowed here after move
 
*x 解引用后對應 String, 賦值給 y 時執(zhí)行 move 語義,所有權(quán)不在了,所以后續(xù) println 不能打印 x
- let y = &*x;
 
可以取字符串的不可變引用來 fix
底層實現(xiàn)
- pub struct Box<
 - T: ?Sized,
 - #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
 - >(Unique<T>, A);
 
上面是 Box 的定義,可以看到是一個元組結(jié)構(gòu)體,有兩個泛型參數(shù):T 代表任意類型,A 代表內(nèi)存分配器。標準庫里 A 是 Gloal 默認值。其中 T 有一個泛型約束 ?Sized, 表示在編譯時可能知道類型大小,也可能不知道,當然一般都用于不知道大小的場景,很少像上文一樣存儲 int
- #[stable(feature = "rust1", since = "1.0.0")]
 - unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Box<T, A> {
 - fn drop(&mut self) {
 - // FIXME: Do nothing, drop is currently performed by compiler.
 - }
 - }
 
這是 Drop 實現(xiàn),源碼里也說了,由編譯器實現(xiàn)
- #[stable(feature = "rust1", since = "1.0.0")]
 - impl<T: ?Sized, A: Allocator> Deref for Box<T, A> {
 - type Target = T;
 - fn deref(&self) -> &T {
 - &**self
 - }
 - }
 - #[stable(feature = "rust1", since = "1.0.0")]
 - impl<T: ?Sized, A: Allocator> DerefMut for Box<T, A> {
 - fn deref_mut(&mut self) -> &mut T {
 - &mut **self
 - }
 - }
 
實現(xiàn)了 Deref 可以定義解引用行為,DerefMut 可變解引用。所以 *x 對應著操作 *(x.deref())
適用場景
官網(wǎng)提到以下三個場景,本質(zhì)上 Box 和普通指針區(qū)別不大,所以用處不如 Rc, Arc, RefCell 廣
- 當類型在編譯期不知道大小,但代碼場景還要求確認類型大小的時候
 - 當你有大量數(shù)據(jù),需要移動所有權(quán),而不想 copy 數(shù)據(jù)的時候
 - trait 對象,或者稱為 dyn 動態(tài)分發(fā)常用在一個集合中存儲不同的類型上,或者參數(shù)指定不同的類型
 
官網(wǎng)有一個鏈表的實現(xiàn)
- enum List {
 - Cons(i32, List),
 - Nil,
 - }
 
上面代碼是無法運行的,道理也很簡單,這是一種遞歸定義。對應 c 代碼也是不行的,我們一般要給 next 類型定義成指針才行
- enum List {
 - Cons(i32, Box<List>),
 - Nil,
 - }
 - use crate::List::{Cons, Nil};
 - fn main() {
 - let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
 - }
 
官網(wǎng)給的解決方案,就是將 next 變成了指針 Box
,  算是常識吧,沒什么好說的















 
 
 





 
 
 
 