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),A、B可同时使用
2.堆上数据类型,A的所有权转移给B,A不再可用
- (三).当所有者离开作用域,这个值被丢弃
三.变量作用域
程序中一个变量的有效范围
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类型的组成部分
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)
- (2).rust规避二次释放(double free)
- 如上示意图所示:变量s1和s2同时指向一块heap,当s1和s2离开对应作用域都屌用drop函数,就存在出现两次释放的问题
- 为了规避二次释放,rust规定 s1 赋值给s2后 s1即失效
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)
(一).函数调用参数传递
(二).返回值与作用域。
返回值也可以转移所有权
(三).变量的所有权遵循模式
- 1.把一个值赋给其它变量时就会发生移动
- 2.当一个持有heap数据的变量离开作用域时,它的值就会被drop函数清理(除非数据的所有权移动到另外一个变量上)
(四).如何避免函数使用值但不发生所有权转移
- 1.函数用完变量后返回值返回变量
- 2.下节的引用
This post is licensed under CC BY 4.0 by the author.