“GC”的新姿势 -- ownership> 编程语言中的“垃圾回收”的本质:找到并回收不再被使用的内存空间
不要动,让我来
JAVA语言中不需要手动去处理内存回收的问题, 虚拟机有完善的垃圾回收机制,成熟的动态内存分配和回收
阿里妹妹推的JVM GC文章
Talk is cheap. Show me the code.
C语言中需要手动分配,手动释放内存空间,自己的屁股自己擦
C语言内存处理
Rust之ownership
一些语言中(如
JAVA
)具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存;在另一些语言(如C
)中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过ownership
系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,ownership
(所有权)系统的任何功能都不会减慢程序。
什么是ownership
?有什么特征?(所有权规则)
- Rust 中的每一个值都有一个被称为其所有者(owner)的变量
{
let s = "hello"; // 这里 s 是 hello 这个值的所有者
}
- 值有且只有一个所有者
fn main() {
let s = String::from("hello");
let a = s;
println!("{}", s);
println!("{}", a);
}
编译结果:
$ rustc only_one_owner.rs
error[E0382]: use of moved value: `s`
--> only_one_owner.rs:4:20
|
3 | let a = s;
| - value moved here
4 | println!("{}", s);
| ^ value used here after move
|
= note: move occurs because `s` has type `std::string::String`, which does not implement the `Copy` trait
error: aborting due to previous error
这里清楚的看到,这里编译不通过,因为当我们let a = s
的时候,其实String::from("hello")
的所有权转移到了变量a
上,所以打印s
是一个不能成功的操作。
修复后的代码以及输出:
fn main() {
let s = String::from("hello");
let a = s;
println!("{}", a);
}
$ rustc only_one_owner.rs
$ ./only_one_owner
hello
- 当所有者(变量)离开作用域,这个值将被丢弃
很显然,这里就是回收的触发点
更多问题:
- 为什么使用
let s = String::from("hello")
不使用let s = "hello"
?
一个是引用,一个是值,值的重新绑定是会直接拷贝的,引用的赋值只是拷贝了指针,长度,容量(如上图)
-
为什么要有只能有一个
owner
的限制呢?
试想如果一个值的所有者是两个变量,那么根据所有权规则第三条,当变量离开作用域的时候,就会释放两次内存(double free
)。是不是想想都很可怕,鬼知道会释放了什么东西。。 -
我就要赋值两次,行不行?
// 你说了算,这里就是 深浅拷贝 的问题了,应该都知道哈
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
调用函数时ownership
的变化
举个🌰:
fn main() {
let s = String::from("hello");
let num = 32;
to(s, num);
println!("main num:{}", num);
}
fn to(str: String, number: i32) {
println!("str={},num={}",str, number);
}
编译运行:
$ ./function_owner
str=hello,num=32
main num:32
## 可以看出,main方法中num依然是可以使用的
修改这个例子:打印出变量s
fn main() {
...
println!("main num:{}, str:{}", num, s);
}
...
// 编译报错:
error[E0382]: use of moved value: `s`
--> function_owner.rs:5:46
|
4 | to(s, num);
| - value moved here
5 | println!("main num:{}, str:{}", num, s);
| ^ value used here after move
|
= note: move occurs because `s` has type `std::string::String`, which does not implement the `Copy` trait
error: aborting due to previous error
本质上,还是上面的问题。
函数返回结果时,ownership
的变化
应该已经很清楚了,会把被调用函数的返回值的所有权返回给调用方作用域中的变量
fn main() {
let s1 = String::from("hello");
let s2 = from(s1);
// 此时,s1已经不能操作,因为调用函数的时候,所有权已经转移了
// s2时可以操作的,from函数的返回值的所有者就是 s2
}
fn from(str: String) {
str
}
你这不行啊,String一旦传递给函数,就不能用了,多浪费啊
考虑下面的代码:
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); //这一步是必须的,因为调用length()的时候,会发生所有权的转移,所有导致 s1 不再可用
let len = length(s1);
println!("'{}'的长度是{}", s2, len);
}
// 计算一个字符串的长度
fn length(s: String) -> usize {
s.len()
}
这里为了实现功能,我们额外clone了一份数据,是不是感觉多次一举,很不优雅,繁琐。。。
- 如何解决传递对象(好像这样叫不太合适。。。)数据,导致所有权的转移?
- 记得当初学C的时候,有一个概念,指针的指针
- Rust上有一个概念 对象的引用,
&s1
表示s1
的引用,并且这种引用并没有所有权,所有权还在被引用的变量(对象)中。 - 函数的参数可以声明成 对象的引用
- Show Time => (原谅我使用这俩个符号,最近在写Scala)
fn main() {
let s1 = String::from("hello");
let len = length(&s1);// 这里传入的是 s1 的引用
println!("'{}'的长度是{}", s1, len); // 这里 s1 的所有权仍在
}
// 计算一个字符串的长度
fn length(s: &String) -> usize {
s.len()
} // s 离开作用域,由于s没有引用值的所有权,所以不会进行内存回收
值,对象,对象引用关系图:
借用
要回收,先分类
马云捡到一个神灯
擦了擦灰后
灯神出现了
灯神:”一个愿望?”
马云:”你说。”
灯神:”…”
最后灯神提出了自己的愿望:
“把我扔了吧。”
马云决定帮它实现这个愿望:
“你是什么垃圾?”
小时候:“妈妈我是从哪里来的?”“你是从垃圾桶里捡来的。”
长大后:“妈妈我是从哪里来的?”“你是从湿垃圾桶里捡来的。”
垃圾分类将至,我从今日开始戒外卖,每天堂食,我将不喝咖啡,不喝奶茶,不食甜品,不吃零食,不买水果,不用护肤品。我是生活中的懒货,懒货中的佼佼者。
网友评论