Rust 各类智能指针、所有权和内存区域关系,值得你看!

2024-05-08 23:04

本文主要是介绍Rust 各类智能指针、所有权和内存区域关系,值得你看!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

导航

  • Rust 细究所有权转移发生了什么
  • 一、各类智能指针指向的值是在栈上还是堆上?
    • 1、Box指向的值的地址在哪
    • 2、Arc和Rc指向的值的地址在哪
    • 3、RefCell和Cell指针指向的地址在哪
      • 1)RefCell
      • 2)Cell
    • 我发现所有权转移,同一份数据,是有可能发生数据地址变化的,不只是单纯的拥有者的变化
  • 二、多线程中所有权转移问题?
    • 1、Box
    • 2、Arc
    • 3、RefCell和Cell

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
  • 可以发现,v1v2的地址和栈的地址差距很大
  • 结论:RcArc指向的数据,和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); //这里没有发生数据转移,直接Copylet 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
  • 可以发现,1Box拥有时,地址是在堆中的,然后转移给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的所有权转移给了mlet 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”)换了一个拥有者
    欢迎大家关注我的博客
    在这里插入图片描述

这篇关于Rust 各类智能指针、所有权和内存区域关系,值得你看!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/971685

相关文章

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Python内存优化的实战技巧分享

《Python内存优化的实战技巧分享》Python作为一门解释型语言,虽然在开发效率上有着显著优势,但在执行效率方面往往被诟病,然而,通过合理的内存优化策略,我们可以让Python程序的运行速度提升3... 目录前言python内存管理机制引用计数机制垃圾回收机制内存泄漏的常见原因1. 循环引用2. 全局变

Springboot项目构建时各种依赖详细介绍与依赖关系说明详解

《Springboot项目构建时各种依赖详细介绍与依赖关系说明详解》SpringBoot通过spring-boot-dependencies统一依赖版本管理,spring-boot-starter-w... 目录一、spring-boot-dependencies1.简介2. 内容概览3.核心内容结构4.

Java中数组与栈和堆之间的关系说明

《Java中数组与栈和堆之间的关系说明》文章讲解了Java数组的初始化方式、内存存储机制、引用传递特性及遍历、排序、拷贝技巧,强调引用数据类型方法调用时形参可能修改实参,但需注意引用指向单一对象的特性... 目录Java中数组与栈和堆的关系遍历数组接下来是一些编程小技巧总结Java中数组与栈和堆的关系关于

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件

java内存泄漏排查过程及解决

《java内存泄漏排查过程及解决》公司某服务内存持续增长,疑似内存泄漏,未触发OOM,排查方法包括检查JVM配置、分析GC执行状态、导出堆内存快照并用IDEAProfiler工具定位大对象及代码... 目录内存泄漏内存问题排查1.查看JVM内存配置2.分析gc是否正常执行3.导出 dump 各种工具分析4.

Python办公自动化实战之打造智能邮件发送工具

《Python办公自动化实战之打造智能邮件发送工具》在数字化办公场景中,邮件自动化是提升工作效率的关键技能,本文将演示如何使用Python的smtplib和email库构建一个支持图文混排,多附件,多... 目录前言一、基础配置:搭建邮件发送框架1.1 邮箱服务准备1.2 核心库导入1.3 基础发送函数二、