返回介绍

4.1 所有权

发布于 2024-10-13 11:25:28 字数 5924 浏览 0 评论 0 收藏 0

所有权(ownership)目的是安全管理内存等资源,及时有效释放。

RAII :任何对象在离开作用域时,析构函数被调用,它所持有资源被释放。

资源属于谁有明确的规则,一旦所有者生命周期结束,则关联资源立即被释放。相比垃圾回收,单一所有者可实现编译期检查,插入自动释放指令。不但高效和安全,且更容易通过代码控制目标生命周期。

规则:

  • 每个值(value)都有一个所有者(owner)。
  • 同一时刻,值只能有一个所有者。
  • 当所有者超出范围(scope),值被释放(drop)。
fn main() {
  {
    let _s = String::from("abc");
  }   // _s drop!
  
  println!("exit");
}

扩展:

  • 所有权可转移(move),新所有者负责释放。
  • 如果值实现 Copy 特征,以复制代替转移。
  • 获取引用(reference),实现非所有权关联。
  • 引用计数( RcArc ),“多” 所有者。

转移

赋值、传参和返回值都可能导致所有权转移。转移后,原绑定失效,成未初始化状态(不能使用,除非重新绑定)。值由新所有者控制生命周期,并负责释放。

fn main() {
  let s = String::from("abc");

  {
    let s2 = s;
         - value moved here

    println!("{:?}", s2);
  }

  println!("{:?}", s);
           ^ error: value borrowed here after move
}
fn main() {
  let s = String::from("abc");
  let t = false;

  if t {
    let _a = s;   // move
  } else {
    let _b = s;   // move
  }

  println!("{:?}", s);
           ^ value borrowed here after move
}
fn main() {
  let s = String::from("abc");
  let t = false;

  while t {
    let _a = s;
         ^ value moved here, in previous iteration of loop
  }
}
fn main() {
  let v = vec![1, 2, 3];

  for e in v {
       - `v` moved due to this implicit call to `.into_iter()`
    println!("{:?}", e);
  }

  println!("{:?}", v);
           ^ value borrowed here after move
}

转移时,可更改可变性。

fn main() {
  let s = String::from("abc");

  let mut s2 = s;
  s2.push_str("def");

  println!("{:?}", s2);
}

不能通过索引(array, vector ...)转移元素所有权。

如要移除元素,可调用容器 popremove 方法,或 std::mem::replace 替换。

fn main() {
  let mut v = vec!["a".to_string(), "b".to_string()];

  // let s = v[0];
  //     ^~~~ error: cannot move out of index

  let s = &mut v[0];
  s.push('!');

  println!("{:?}", v);
}

如果值实现了 Copy 特征,表示可复制,就不存在转移问题。标量类型( i32 , ...)实现了复制特征,且元素为复制类型的元组和数组也会复制。

注意, Copy 是隐式调用, Clone 需要显式调用。

fn main() {
  let s = [1, 2, 3, 4];

  {
    let s2 = s;       // copy
    println!("{:?}", s2);
  }

  println!("{:?}", s);
}

实现

利用反汇编观察所有权转移前后的释放操作。

fn main() {
  let _s = String::from("abc");
}
=> 0x000055555555a85b <+11>:  mov  rdi,rsp
   0x000055555555a85e <+14>:  mov  rsi,rax
   0x000055555555a861 <+17>:  mov  edx,0x3
   0x000055555555a866 <+22>:  call   0x55555555a110 <...::from>

   0x000055555555a86b <+27>:  mov  rdi,rsp
   0x000055555555a86e <+30>:  call   0x55555555a2e0 <...::drop_in_place>

释放操作由编译器插入的 drop_in_place 完成。新所有者超出作用域后,立即释放。

fn main() {
  let s = String::from("abc");

  {
    let _s2 = s;  // 转移。
  }         // 此处释放!

  let _x = 100;   // 识别外层作用域。
}
1  fn main() {

2    let s = String::from("abc");
   0x000055555555a85b <+11>:  mov  rdi,rsp
   0x000055555555a85e <+14>:  mov  rsi,rax
   0x000055555555a861 <+17>:  mov  edx,0x3
   0x000055555555a866 <+22>:  call   0x55555555a110 <...::from>

3  
4    {
5      let _s2 = s;
   0x000055555555a86b <+27>:  mov  rax,QWORD PTR [rsp]
   0x000055555555a86f <+31>:  mov  QWORD PTR [rsp+0x18],rax ; _s2.ptr
   ...                            ; _s2.cap, len

6    }
   0x000055555555a888 <+56>:  lea  rdi,[rsp+0x18]       ; _s2
   0x000055555555a88d <+61>:  call   0x55555555a2e0 <...::drop_in_place>

7  
8    let _x = 100;
9  }

传递

从语义看,复制操作克隆新实体,其后各不相干。转移则将目标对象 “移交” 给新所有者。

fn test(s: String) {
  let _s2 = s;
}

fn main() {
  let s = String::from("abc");
  test(s);
} 
(gdb) b 2  
(gdb) b 7
(gdb) r


(gdb) disass/s  ; 反汇编 main 函数。

5  fn main() {

6    let s = String::from("abc");
   0x000055555555922b <+11>:  lea  rdi,[rsp+0x8]
   0x0000555555559230 <+16>:  mov  rsi,rax
   0x0000555555559233 <+19>:  mov  edx,0x3
   0x0000555555559238 <+24>:  call   0x55555555aae0 <...::from>

7    test(s);
=> 0x000055555555923d <+29>:  mov  rax,QWORD PTR [rsp+0x8]
   0x0000555555559242 <+34>:  mov  QWORD PTR [rsp+0x20],rax ; s.ptr
   ...                            ; s.cap, len

   0x000055555555925b <+59>:  lea  rdi,[rsp+0x20]       ; rdi --> test
   0x0000555555559260 <+64>:  call   0x5555555591f0 <demo::test>

8  } 
   0x0000555555559265 <+69>:  add  rsp,0x38
   0x0000555555559269 <+73>:  ret  
End of assembler dump.


(gdb) b *0x0000555555559260  ; 调用 test 前。
(gdb) c

(gdb) x/2xg $rsp+0x8     ; 函数 from 返回值。
0x7fffffffe338:  0x000055555559e9d0  0x0000000000000003

(gdb) x/s 0x000055555559e9d0 ; 字符串内容序列。
0x55555559e9d0:  "abc"

(gdb) x/2xg $rsp+0x20    ; 本地变量 s 内容。
0x7fffffffe350:  0x000055555559e9d0  0x0000000000000003


(gdb) c
(gdb) disass/s  ; 反汇编 test 函数。

1  fn test(s: String) {

2    let _s2 = s;
=> 0x00005555555591f4 <+4>:    mov  rax,QWORD PTR [rdi]    ; s.ptr
   0x00005555555591f7 <+7>:    mov  QWORD PTR [rsp],rax    ; _s2
   0x00005555555591fb <+11>:  mov  rax,QWORD PTR [rdi+0x8]  ; s.cap
   0x00005555555591ff <+15>:  mov  QWORD PTR [rsp+0x8],rax
   0x0000555555559204 <+20>:  mov  rax,QWORD PTR [rdi+0x10] ; s.len
   0x0000555555559208 <+24>:  mov  QWORD PTR [rsp+0x10],rax

3  }
   0x000055555555920d <+29>:  mov  rdi,rsp  ; _s2.drop!
   0x0000555555559210 <+32>:  call   0x55555555ac30 <...::drop_in_place>

可以看出,栈上的数据被复制。新拥有者负责释放堆内存,也就是说 drop 只会执行一次。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文