简介
首先,对于那些没有手动分配释放内存的语言例如Java和C#(当然也包括python),它们拥有自动的垃圾收集器,用于寻找和收集没使用的内存,对内存进行管理。而像C这样的语言,程序员使用malloc()
和free()
这样的函数手动进行内存分配和释放。垃圾收集器:(考虑单开一篇写?)
而Rust使用其独特的所有权系统来管理内存。其包含一种在编译时检查的规则,而不在运行时检查,因此没有开销,不会降低程序运行的速度。
在Stack上的数据必须拥有固定的大小,这个课讲的stack和heap很浅显不太涉及操作系统,更多像是数据结构层面的内容。指令在内存中跳转次数越少,访问速度就越快。而且类似于
_cdecl
方式,函数本地变量压栈结束回复
所有权可以跟踪代码哪些部分正在使用堆上的哪些数据,同时最小化堆上的重复数据量,清理堆上的未使用数据。类似一个编译时运行的堆管理器?“管理堆是所有权存在的原因。”
所有权规则
scope:作用域
-
每个值都有一个变量,我们称这个变量是这个值的所有者
-
每个值同时只能有一个所有者
-
当所有者超出作用域时,该值即被删除
以string类型为例,string类型可以一定程度上代替所有预先定义或自己创建的复杂数据类型。下面看一个简单的例子:
1 | fn main(){ |
rust没有GC,因此使用所有权机制来管理内存的释放,而且能避免double free。参考规则3,变量离开作用域的时候,rust会调用drop()
函数直接对齐进行释放。
移动Move
首先看这样的代码:
1 | let x=5; |
而在面对复杂数据类型的时候就不一样了,string类型本身类似于一个结构体:
字段 | 值 |
---|---|
ptr | 指向实际内容 |
len | 字符串长度 |
capacity | 总申请的容量 |
而聪明的编译器在下面的代码中: |
1 | let s1=String::from("hello"); |
对s2只复制ptr而不复制整个一份内容,但如果按照先前的简单规则,s2和s1离开作用域的时候都会尝试释放一块内存,直接double free了。显然我们不允许这种情况发生,那么rust
采用的是让s1失效,即将s1“移动”到了s2。s2在这样声明后s1直接就用不了了。如果要想在s2声明后再使用s1,需要调用特定的方法如clone()
。UAF达咩。
这里也体现了rust的思想:不自动为任何数据自动创建深拷贝,换言之,就运行时性能而言,任何自动赋值的操作都是廉价的。
复制与克隆
1 | let s1=String::from("hello"); |
rust真的嗯限制,它有一个trait(接口?)叫做copy能实现我们一般意义上的赋值。但是实现了copy的类型就不会有drop,实现了drop就不允许有copy了。所以不要指望堆上分配的东西能copy了。一般来说简单的标量(整型浮点型boolchar)都可copy,由可copy变量构成的tuple可copy。
函数传参
传值给函数将发生移动或复制。将可复制的量传给函数就会发生复制,不可复制的量传给函数就会发生移动,即传完后后面就不能用了。所有权发生转移。
1 | fn main(){ |
同样,函数返回值也会发生所有权转移。思路和传参类似:
1 | fn gives_ownership() -> String{ |
s的所有权此时就会转移给s1。综上,所有权的转移遵循这样的模式:
-
将一个值赋给第二个变量时,所有权就会转移(一般发生移动)
-
堆变量离开作用域时,rust会调用drop函数对它进行清理除非它的所有权被移动到另一个变量上了。
传递参数但是不给予所有权?
基于上面的理解一种最简单的方法是将参数传进去然后再返回来:
1 | fn take_and_giveback(s1: String) -> (String, u64) { |
引用和借用
即不取得所有权而使用值。用&String
类型传入参数,这种行为称为借用。借用的变量默认不可修改,想引用修改必须要这样设置:
1 | let mut s1=String::from("text");//1. 创建的时候变量本身可变 |
而且rust还有这样的限制:
-
特定作用域内对某一数据只允许同时存在至多一个可变引用。
-
不允许同时存在可变引用和不可变引用。但多个读(不可变引用)可以同时存在。即下面的操作是非法的:
1 | let mut s1=String::from("text");//1. 创建的时候变量本身可变 |
防止了数据竞争(数据库?)。要想同时使用两个或以上的可变引用则需要手动控制作用域:
1 | let mut s1=String::from("text");//1. 创建的时候变量本身可变 |
也就是要么就一个可变引用要么就没有可变引用。
这种类似编译加锁的机制防止UAF存在?真能?
切片slice
这种操作是单独针对字符串(或数组)的,类似python的切片方法,它不获得所有权。
1 | let s=String::from("Hello World"); |
这样的形式称为字符串切片,是指向字符串中一部分内容的引用。它并不copy内容,也是返回一个包含指针的结构体类似的东西,含一个指针一个长度。它的类型是&str
,本质是个不可变引用,而const字符串在rust中是&str
类型,即一个二进制程序字节的切片。
这样写函数会好:
1 | fn all_type(s:&str)->&str{ |
数组同理&a[1..3]