导航
Rust 细究智能指针和所有权转移发生了什么
- 发这篇博客的原因就是在学习
Rust
过程中,涉及到很多栈、堆和所有权的问题,导致我开始不清楚一些变量到底是分配在栈上还是堆上,还有所有权发生变化是否发生了内存区域的移动等等 - 还有下面这种又是什么情况,智能指针指向的值到底在哪?等等一系列问题,emo了~下定决心搞清楚它
let a=1;
let b=Box::new(a);//其实是堆上Copy一个1
一、各类智能指针指向的值是在栈上还是堆上?
1、Box指向的值的地址在哪
fn main() {
let mut v1 =1;
let b_ptr2 = ptr::addr_of!(v1);
println!("栈的地址:{:p}", b_ptr2);
let mut v2 =Box::new(String::from("1"));
let b_ptr2 = ptr::addr_of!(*v2);
println!("String::from在堆中的地址:{:p}", b_ptr2);
let v3 =String::from("1");
let b_ptr2 = ptr::addr_of!(v3);
println!("v3变量原来在栈中的地址:{:p}", b_ptr2);
let t=Box::new(v3);
let b_ptr2 = ptr::addr_of!(*t);
println!("v3的所有权转移给Box后,v3 的地址:{:p}", b_ptr2);
}
运行一下
栈的地址:0x7ff7b6e931ec
String::from在堆中的地址:0x7fb413f05e20
v3变量原来在栈中的地址:0x7ff7b6e932a0
v3的所有权转移给Box后,v3 的地址:0x7fb413f05e50
- 可以发现,运行结果的第四行和第二行,地址的结果最接近,说明
String::from("1")
的所有权转移给Box
后,String::from(“1”)
从栈中转移
到堆中了,并且肯定不是Copy,因为String类型没有Copy特征,也肯定不是Clone,因为转移后也无法再次打印v3的地址,你可以试试 - 结论:
Box
指向的内容,一定是在堆上的,原来在栈上的也会转移到堆上
2、Arc和Rc指向的值的地址在哪
use std::ptr;
use std::rc::Rc;
use std::sync::*;
use std::sync::{Condvar, Mutex};
use std::thread;
fn main() {
let mut v1 = 1;
let b_ptr2 = ptr::addr_of!(v1);
println!("栈的地址:{:p}", b_ptr2);
let mut v1 = Rc::new(1);
let b_ptr2 = ptr::addr_of!((*v1));
println!("v1 变量的地址:{:p}", b_ptr2);
let mut v2 = Arc::new(1);
let b_ptr2 = ptr::addr_of!((*v2));
println!("v2 变量的地址:{:p}", b_ptr2);
}
运行一下
栈的地址:0x7ff7b1cc62a4
v1 变量的地址:0x7f7c31705e20
v2 变量的地址:0x7f7c31705e40
- 可以发现,
v1
和v2
的地址和栈的地址差距很大 - 结论:
Rc
和Arc
指向的数据,和Box
一样,都会分配在堆上。
3、RefCell和Cell指针指向的地址在哪
1)RefCell
use std::cell::{Cell, RefCell};
use std::ptr;
use std::rc::Rc;
use std::sync::*;
use std::sync::{Condvar, Mutex};
use std::thread;
fn main() {
let mut v1 = 1;
let b_ptr2 = ptr::addr_of!(v1);
println!("栈的地址:{:p}", b_ptr2);
let mut v3 = Box::new(String::from("1"));
let b_ptr2 = ptr::addr_of!(*v3);
println!("String::from()在被Box拥有时,在堆中的地址:{:p}", b_ptr2);
let mut v4 = RefCell::new(*v3); //String::from("1")发生所有权转移
let s1 = v4.borrow();
let b_ptr2 = ptr::addr_of!((*s1));
println!("在RefCell拥有时,String::from() 的地址:{:p}", b_ptr2);
// println!("{}",v3);无法打印,可以解开验证一下。
}
我们运行一下
栈的地址:0x7ff7b16dd254
String::from()在被Box拥有时,在堆中的地址:0x7f7ed0f05e20
在RefCell拥有时,String::from() 的地址:0x7ff7b16dd310
- 可以发现,
String::from()
被Box
拥有时,地址是在堆中的,然后转移给RefCell
时,数据又移动到栈中了,同一份数据,两次地址完全不同,连区域都变了 - 结果:
RefCell
指向的值,在栈上。
2)Cell
use std::cell::{Cell, RefCell};
use std::ptr;
use std::rc::Rc;
use std::sync::*;
use std::sync::{Condvar, Mutex};
use std::thread;
fn main() {
let mut v1 = 1;
let b_ptr2 = ptr::addr_of!(v1);
println!("栈的地址:{:p}", b_ptr2);
let mut v3 = Box::new(1);
let b_ptr2 = ptr::addr_of!(*v3);
println!("1在被Box拥有时,在堆中的地址:{:p}", b_ptr2);
let mut v4 = Cell::new(*v3); //这里没有发生数据转移,直接Copy
let s1=v4.get();
let b_ptr2 = ptr::addr_of!(s1);
println!("在Cell拥有时,1的地址:{:p}", b_ptr2);
//println!("{}",v3);//可以打印,可以解开验证一下。
}
运行一下
栈的地址:0x7ff7b628c2ac
1在被Box拥有时,在堆中的地址:0x7ff61bf05e10
在Cell拥有时,1 的地址:0x7ff7b628c34c
- 可以发现,
1
被Box
拥有时,地址是在堆中的,然后转移给Cell
时,数据是Copy到栈中了,是两个值,两个值的地址完全不同,连区域都变了.因为1是i32类型,i32具备Copy特征。 Cell
只能用在具有Copy
特征的类型上- 结果:
Cell
指向的值,在栈上
写到这里
我发现所有权转移,同一份数据,是有可能发生数据地址变化的,不只是单纯的拥有者的变化
二、多线程中所有权转移问题?
1、Box
use std::cell::{Cell, RefCell};
use std::ptr;
use std::rc::Rc;
use std::sync::*;
use std::sync::{Condvar, Mutex};
use std::thread;
fn main() {
let mut v1 = 1;
let b_ptr2 = ptr::addr_of!(v1);
println!("主线程栈的地址:{:p}", b_ptr2);
let mut v3 = Box::new(String::from("1"));
let b_ptr2 = ptr::addr_of!(*v3);
println!("1在主线程的Box拥有时,在堆中的地址:{:p}", b_ptr2);
let handle = thread::spawn(move || {
let mut v1 = 1;
let b_ptr2 = ptr::addr_of!(v1);
println!("异步线程内栈的地址:{:p}", b_ptr2);
let m = v3; //主线程Box持有的1的所有权转移给了m
let b_ptr2 = ptr::addr_of!(*m);
println!("1在异步线程内的Box拥有时,在堆中的地址:{:p}", b_ptr2);
});
//print!("{}", v3); //不能打印,已经转移给了线程内部
handle.join().unwrap();
//println!("{}",v3);//可以打印,可以解开验证一下。
}
- 运行一下
主线程栈的地址:0x7ff7b7a0b2b4
1在主线程的Box拥有时,在堆中的地址:0x7f7de9f05e20
异步线程内栈的地址:0x70000a323bdc
1在异步线程内的Box拥有时,在堆中的地址:0x7f7de9f05e20
- 可以看到,通过
move
关键字我们可以将一个值的所有权移动到另一个线程 - 还可以知道,没有Copy特性数据的所有权转移,同一份数据的地址没有发生改变,只是String::from(“1”)的拥有者变了。
- Copy特性的数据,会复制一份数据,大家可以把String::from(“1”)换成1试试看,篇幅原因我就不展示了
2、Arc
因为是多线程,我们不讨论Rc,因为Rc只能在单线程中正常使用,Arc可以实现在多线程中复制一个值的所有权,但是不能所有权拿走
先说重要结果,Arc拥有的值,不允许在单、多线程中转移所有权,Box可以。看一下代码
use std::cell::{Cell, RefCell};
use std::ptr;
use std::rc::Rc;
use std::sync::*;
use std::sync::{Condvar, Mutex};
use std::thread;
fn main() {
let mut v1 = 1;
let b_ptr2 = ptr::addr_of!(v1);
println!("主线程栈的地址:{:p}", b_ptr2);
let mut v3 = Arc::new(String::from("1"));
let b_ptr2 = ptr::addr_of!(*v3);
println!("1在主线程的Box拥有时,在堆中的地址:{:p}", b_ptr2);
let handle = thread::spawn(move || {
let mut v1 = 1;
let b_ptr2 = ptr::addr_of!(v1);
println!("异步线程内栈的地址:{:p}", b_ptr2);
let m = *v3; //!!!!!!!编译错误
let b_ptr2 = ptr::addr_of!(*m);
println!("1在异步线程内的Box拥有时,在堆中的地址:{:p}", b_ptr2);
});
handle.join().unwrap();
}
3、RefCell和Cell
这个是控制可变性和绕过编译器借用规则,并且RefCell的borrow和borrow_mut 不会抢走所有权。我们来看一下
use std::cell::{Cell, Ref, RefCell};
use std::ptr;
use std::rc::Rc;
use std::sync::*;
use std::sync::{Condvar, Mutex};
use std::thread;
fn main() {
let mut v1 = 1;
let b_ptr2 = ptr::addr_of!(v1);
println!("主线程栈的地址:{:p}", b_ptr2);
let v3 = RefCell::new(String::from("1"));
let b_ptr2 = ptr::addr_of!(v3);
println!("RefCell的地址:{:p}", b_ptr2);
let handle = thread::spawn(move || {
let mut v1 = 1;
let b_ptr2 = ptr::addr_of!(v1);
println!("RefCel在异步线程内栈的地址:{:p}", b_ptr2);
let m = v3; //编译错误
let b_ptr2 = ptr::addr_of!(m);
println!("RefCell的地址:{:p}", b_ptr2);
});
handle.join().unwrap();
}
运行一下
主线程栈的地址:0x7ff7b5993294
RefCell::new(String::from()的地址:0x7ff7b59932e0
RefCell::new(String::from()在异步线程内栈的地址:0x7000066ddad4
RefCell::new(String::from()的地址:0x7000066ddb20
- 我没有在主函数中,调用v3的.borrow,因为借用后不能发生所有权转移
- 其次,我暂时没有办法不通过borrow的方式访问
RefCell
指向的值的地址
,如果用inner_into
,也会发生所有权转移。头大~ - 所以上面只能简单验证了RefCell作为一个变量在线程之间的所有权转移,String::from(“1”)换了一个拥有者
欢迎大家关注我的博客