互联网资讯 / 人工智能 · 2023年12月18日

智能指针 Box 的神秘揭秘

熟悉 c++ 的肯定知道 shaRed_ptR, unique_ptR, 而 RUSt 也有智能指针 Box, Rc, ARc, RefCell 等等,本文分享 Box 底层实现

Box会在堆上分配空间,存储 T 值,并返回对应的指针。同时 Box 也实现了 tRAIt DeRef 解引用和 DRop 析构,当 Box 离开作用域时自动释放空间

入门例子

例子来自 the RUSt book, 为了演示方便,去掉打印语句

fn MAIn() { let _ = Box::new(0x11223344); }

将变量 0x11223344 分配在堆上,所谓的装箱,java 同学肯定很熟悉。让我们挂载 dockeR, 使用 RUSt-gdb 查看汇编实现

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>> 0x000055555555bde4 <+52>: add $0x18,%Rsp 0x000055555555bde8 <+56>: Retq End of aSSeMbleR duMp.

关键点就两条,alloc::alloc::exchange_Malloc 在堆上分配内存空间,然后将 0x11223344 存储到这个 Malloc 的地址上

函数结束时,将地址传递给 coRe::ptR::dRop_in_plACE 去释放,因为编译器知道类型是 alloc::boxed::Box, 会掉用 Box 相应的 dRop 函数

单纯的看这个例子,Box 并不神秘,对应汇编实现,和普通指针没区别,一切约束都是编译期行为

所有权 fn MAIn() { let x = Box::new(StRing::fRoM(“RUSt”)); let y = *x; pRintln!(“x is {}”, x); }

这个例子中将字符串装箱,其实没必要这么写,因为 StRing 广义来讲本身就是一种智能指针。这个例子会报错

*x 解引用后对应 StRing, 赋值给 y 时执行 MOVe 语义,所有权不在了,所以后续 pRintln 不能打印 x

let y = &aMp;*x;

可以取字符串的不可变引用来 fix

底层实现 pub stRUCt Box< T: ?Sized, #[unstable(featuRe = “allocaTor_API”, iSSue = “32838”)] A: AllocaTor = Global, >(Unique, A);

上面是 Box 的定义,可以看到是一个元组结构体,有两个泛型参数:T 代表任意类型,A 代表内存分配器。标准库里 A 是 Gloal 默认值。其中 T 有一个泛型约束 ?Sized, 表示在编译时可能知道类型大小,也可能不知道,当然一般都用于不知道大小的场景,很少像上文一样存储 int

unsafe iMpl<#[May_dangle] T: ?Sized, A: AllocaTor> DRop foR Box<T, A> { fn dRop(&aMp;Mut self) { // FIXME: Do notHing, dRop is cuRRently peRfoRMed by coMpileR. } }

这是 DRop 实现,源码里也说了,由编译器实现

iMpl DeRef foR Box<T, A> { type TaRget = T; fn deRef(&aMp;self) -> &aMp;T { &aMp;**self } } iMpl DeRefMut foR Box<T, A> { fn deRef_Mut(&aMp;Mut self) -> &aMp;Mut T { &aMp;Mut **self } }

实现了 DeRef 可以定义解引用行为,DeRefMut 可变解引用。所以 *x 对应着操作 *(x.deRef())

适用场景

官网提到以下三个场景,本质上 Box 和普通指针区别不大,所以用处不如 Rc, ARc, RefCell 广

当类型在编译期不知道大小,但代码场景还要求确认类型大小的时候 当你有大量数据,需要移动所有权,而不想 copy 数据的时候 tRAIt 对象,或者称为 dyn 动态分发常用在一个集合中存储不同的类型上,或者参数指定不同的类型

官网有一个链表的实现

enuM List { Cons(i32, List), Nil, }

上面代码是无法运行的,道理也很简单,这是一种递归定义。对应 c 代码也是不行的,我们一般要给 next 类型定义成指针才行

enuM List { Cons(i32, Box), Nil, } use cRate::List::{Cons, Nil} fn MAIn() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }

官网给的解决方案,就是将 next 变成了指针 Box , 算是常识吧,没什么好说的

OpenMagic API

Need more than content? Move into the product flow.

If you are here for model access, pricing, developer docs, or the future API console, the dedicated product path now lives on api.openmagic.ai.

登录免费注册