Post

04所有权·1什么是所有权

介绍rust的一个特性:所有权之什么是所有权

一.Stack VS Heap(2023010)

(一).Stack

  • 特性
    • ①.LIFO
    • ②.栈的数据大小已知道且固定
    • ③.入栈比在堆上分配内存快。(因为在堆上分配内存需经过操作系统搜索空余内存,并返回空余内存指针。)
  • 函数调用传递参数值和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。
    • 实际这跟cpu架构(X86)和语言函数调用规范相关联
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      Example1 PROC
          LOCAL temp:DWORD
          mov eax,temp
          ret
      Example1 ENDP
    
      push ebp
      mov ebp, esp			;栈指针
      add esp, OFFFFFFFCh     ;ESP 加 -4
      mov eax, [ebp-4]
      leave					;//等价mov esp,ebp; pop ebp 
      ret
    
    • stdcall,cdecl,thiscall,fastcall,regparm(n)

(二).Heap

  • 特性
    • ①.存放编译时大小未知或大小变化的数据
    • ②.访问Heap比访问Stack慢(必须通过指针访问数据)
  • 向Heap读写数据步骤
    • 写入数据
      • ①.在Heap上_分配内存(allocating on the heap)
      • ②.返回指针(分配内存的地址)
      • ③.拷贝数据到指针指向的内存
    • 读取数据
      • ①.通过指针指向Heap内存
      • ②.获取内存数据

二.所有权的规则

  • (一).每个值都有一个所有者
  • (二).任意时刻,值有且只有一个所有者
1
2
3
//变量A 赋值给 变量B 情况:
1.栈上数据类型准确的是实现了Copy trait,AB可同时使用
2.堆上数据类型A的所有权转移给BA不再可用
  • (三).当所有者离开作用域,这个值被丢弃

三.变量作用域

程序中一个变量的有效范围

1
2
3
4
{// s 在这里无效, 它尚未声明
    let s = "hello";   // 从此处起,s 是有效的
    // 使用 s
} // 此作用域已结束,s 不再有效

四.String类型

(一).字符串字面值

代码手写的字符串值。是不可变的:编译时计算出所占大小、内容被硬编码到执行文件、执行速度快且高效

(二).String类型

  • 在Heap上分配内存
  • String类型在作用域结束后rust自动释放(drop)其申请的内存
1
2
3
4
5
//比如:
{
    let s = String::from("hello"); // 从此处起,s是有效的
    //...
} // 此作用域已结束,rust编译器自动插入调用drop
  • 创建String类型: let s=String::from("hahah");

五.变量与数据的交互方式

(一).move

1.栈上数据类型移动

标量数据的移动发生copy动作(因为标量数据实现了Copy trait)

1
2
3
//比如:
let x = 5;
let y = x;//将5绑定到x;接着生成一个值x的拷贝并绑定到y(拷贝后仍然有效)

2.堆上数据类型移动。

  • (1).拷贝堆数据类型的结构体,并没有复制指针指向的数据.比如String类型:
    • ①.String类型的组成部分

    04-01.png

    1
    2
    3
    4
    5
    
      // 1.左边(存储在栈上)
      //      ptr:指向heap的指针
      //      capacity:申请的heap的容量
      //      len:当前字符串长度
      //  2.右边:堆上申请的内存
    
    • ②.将String类型赋值给另外一个变量:只复制结构体没有拷贝指针指向内存
    1
    2
    3
    
      let s1=String::from("hello");
      let s2=s1;//s1赋值给s2的示意图如下
      //有点类似:java的浅拷贝(shallow copy)
    

    04-02.png

  • (2).rust规避二次释放(double free)
    • 如上示意图所示:变量s1和s2同时指向一块heap,当s1和s2离开对应作用域都屌用drop函数,就存在出现两次释放的问题
    • 为了规避二次释放,rust规定 s1 赋值给s2后 s1即失效

    04-03.png

    1
    2
    3
    4
    5
    
      let s1 = String::from("hello");
      let s2 = s1;
      println!("{}, world!" , s1);//这里抛出错误 :^^ value used here after move.
    
      //符合所有权规则2:任意时刻,值有且只有一个所有者。所以赋值实际就是所有权的转移
    

(二).clone和copy

1.栈上数据的copy

1
2
3
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);//虽然y没有copy  x,但是x不会移动失效
  • (1).规则
    • ①.实现了 Copy trait注解的类型,从一个变量赋值给另外一个变量后仍然可用
    • ②.Copy trait和Drop trait互斥.(Rust 不允许自身或其任何部分实现了 Drop trait 的类型使用 Copy trait)
  • (2).实现了Copy trait的数据类型
    • ①.标量类型:所有整数、布尔、浮点数、字符串(char)

      际上都是拷贝成本基本可以忽略的数据类型。比如一个i32或u32在32cpu架构中寄存器(eax,ebx..)就可以容纳不过是一条move指令

    • ②.元组。当且仅当其包含的类型也都是 Copy trait的时候

      比如, (i32, i32) 是 Copy 的,但(i32, String) 就不是。

2.堆上数据的clone

1
2
3
let s1 = String::from("hello");
let s2 = s1.clone();//clone 是通用函数
println!("s1 = {}, s2 = {}", s1, s2);//s2复制了s1指向的堆内存,现在编译时不会报错了

六.所有权与函数(20230105)

(一).函数调用参数传递

  • 将值传递给函数与给变量赋值的原理相似。 04-04

  • 向函数传递值可能会移动或者复制

(二).返回值与作用域。

返回值也可以转移所有权

04-05

(三).变量的所有权遵循模式

  • 1.把一个值赋给其它变量时就会发生移动
  • 2.当一个持有heap数据的变量离开作用域时,它的值就会被drop函数清理(除非数据的所有权移动到另外一个变量上)

(四).如何避免函数使用值但不发生所有权转移

  • 1.函数用完变量后返回值返回变量
  • 2.下节的引用
This post is licensed under CC BY 4.0 by the author.