揭开智能指针 Box 的神秘面纱

人工智能
Box<T> 会在堆上分配空间,存储 T 值,并返回对应的指针。同时 Box 也实现了 trait Deref 解引用和 Drop 析构,当 Box 离开作用域时自动释放空间。

[[416792]]

本文转载自微信公众号「董泽润的技术笔记」,作者董泽润。转载本文请联系董泽润的技术笔记公众号。

熟悉 c++ 的肯定知道 shared_ptr, unique_ptr, 而 Rust 也有智能指针 Box, Rc, Arc, RefCell 等等,本文分享 Box 底层实现

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

入门例子

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

  1. fn main() { 
  2.     let _ = Box::new(0x11223344); 

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

  1. Dump of assembler code for function hello_cargo::main: 
  2.    0x000055555555bdb0 <+0>: sub    $0x18,%rsp 
  3.    0x000055555555bdb4 <+4>: movl   $0x11223344,0x14(%rsp) 
  4. => 0x000055555555bdbc <+12>: mov    $0x4,%esi 
  5.    0x000055555555bdc1 <+17>: mov    %rsi,%rdi 
  6.    0x000055555555bdc4 <+20>: callq  0x55555555b5b0 <alloc::alloc::exchange_malloc> 
  7.    0x000055555555bdc9 <+25>: mov    %rax,%rcx 
  8.    0x000055555555bdcc <+28>: mov    %rcx,%rax 
  9.    0x000055555555bdcf <+31>: movl   $0x11223344,(%rcx) 
  10.    0x000055555555bdd5 <+37>: mov    %rax,0x8(%rsp) 
  11.    0x000055555555bdda <+42>: lea    0x8(%rsp),%rdi 
  12.    0x000055555555bddf <+47>: callq  0x55555555bd20 <core::ptr::drop_in_place<alloc::boxed::Box<i32>>> 
  13.    0x000055555555bde4 <+52>: add    $0x18,%rsp 
  14.    0x000055555555bde8 <+56>: retq 
  15. End of assembler dump. 

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

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

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

所有权

  1. fn main() { 
  2.     let x = Box::new(String::from("Rust")); 
  3.     let y = *x; 
  4.     println!("x is {}", x); 

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

  1. 3 |     let y = *x; 
  2.   |             -- value moved here 
  3. 4 |     println!("x is {}", x); 
  4.   |                         ^ value borrowed here after move 

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

  1. let y = &*x; 

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

底层实现

  1. pub struct Box< 
  2.     T: ?Sized, 
  3.     #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global
  4. >(Unique<T>, A); 

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

  1. #[stable(feature = "rust1", since = "1.0.0")] 
  2. unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Box<T, A> { 
  3.     fn drop(&mut self) { 
  4.         // FIXME: Do nothing, drop is currently performed by compiler. 
  5.     } 

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

  1. #[stable(feature = "rust1", since = "1.0.0")] 
  2. impl<T: ?Sized, A: Allocator> Deref for Box<T, A> { 
  3.     type Target = T; 
  4.  
  5.     fn deref(&self) -> &T { 
  6.         &**self 
  7.     } 
  8.  
  9. #[stable(feature = "rust1", since = "1.0.0")] 
  10. impl<T: ?Sized, A: Allocator> DerefMut for Box<T, A> { 
  11.     fn deref_mut(&mut self) -> &mut T { 
  12.         &mut **self 
  13.     } 

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

适用场景

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

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

官网有一个链表的实现

  1. enum List { 
  2.     Cons(i32, List), 
  3.     Nil, 

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

  1. enum List { 
  2.     Cons(i32, Box<List>), 
  3.     Nil, 
  4.  
  5. use crate::List::{Cons, Nil}; 
  6.  
  7. fn main() { 
  8.     let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); 

 

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

 

责任编辑:武晓燕 来源: 董泽润的技术笔记
相关推荐

2015-08-20 13:43:17

NFV网络功能虚拟化

2021-06-07 08:18:12

云计算云端阿里云

2010-05-17 09:13:35

2014-03-12 11:11:39

Storage vMo虚拟机

2010-05-26 19:12:41

SVN冲突

2018-03-01 09:33:05

软件定义存储

2009-06-01 09:04:44

Google WaveWeb

2023-11-02 09:55:40

2009-09-15 15:34:33

Google Fast

2016-04-06 09:27:10

runtime解密学习

2024-02-14 09:00:00

机器学习索引ChatGPT

2016-11-16 09:06:59

2010-05-11 10:19:17

VMforceJava云计算

2021-07-28 21:49:01

JVM对象内存

2011-08-02 08:59:53

2017-10-16 05:56:00

2010-06-17 10:53:25

桌面虚拟化

2021-05-25 09:01:21

Linux命令Bash histor

2021-09-17 15:54:41

深度学习机器学习人工智能

2020-04-14 10:44:01

区块链渗透测试比特币
点赞
收藏

51CTO技术栈公众号