浅谈Rust内存管理
Rust因在内存管理上的独到之处,近年来受到了不少开发者的青睐。Rust内存管理的核心功能就是所有权。不同的语言采取了不同的内存管理方式,主要分为开发者手动管理或者编译器辅助管理,以及垃圾回收机制等。Rust的所有权机制,有别于这两者。
堆栈内存
我们知道程序会在堆或者栈上创建数据。栈上创建数据很容易,只要知道数据的大小,移动栈顶指针就能开辟出所需要的空间。对它的访问,代码层面我们有变量,汇编层面只是一个相对于栈顶地址的偏移而已,这个偏移量往往很小,所以访问栈数据很快。而堆上创建数据,则比较麻烦,我们要从内存空间,寻找一块合适大小的内存,然后“开辟”出来,也就是需要登记下,哪一块空间被使用了。然后需要存储这个地址的开始地址,占用大小,实际使用大小等。访问这块内存的话,也要根据记录的开始地址,去内存空间里寻址,这个过程相对比较慢。
我们使用堆内存,是因为它有一些栈内存没有的好处:1.可以动态开辟。栈中的所有数据都必须占用已知且固定的大小。我们在编译阶段,已经能知道栈上的变量的相对位置关系和大小。而我们可以根据需要,开辟任意允许大小的堆上数据。开辟的这些数据,我们也无法确定它们之间的位置关系。2.堆内存可以共享。因为指针和实际内存的分离,使得我们可以使多个指针指向同一块内存来实现数据共享。但是因为栈变量即栈内存,所以两个变量也就意味着两份存储。3.可以操控的生命周期。一旦发生清栈,栈上开辟的内存就会被销毁。而堆上的内存可以存在很久,有些语言里需要开发者手动清除,有一些需要一些回收机制来清理。
所有权规则
我们先提出Rust的所有权规则:
- Rust 中的每一个值都有一个 所有者(owner)。
- 值在任一时刻有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
这里的作用域指的是变量的有效使用范围。基于我们前面对堆栈的认识,我们发现,栈上的变量是符合上述描述的。栈变量即栈内存,变量既是值的唯一所有者,当我们离开变量的作用域,编译器不允许我们再次使用这个变量,此时它已经被丢弃,后续清栈的时候,它就会被销毁。但是对于堆内存,上述规则在C、C++等语言里就不符合了。有些语言通过在变量作用域结束的地方插入释放变量所指内存的代码,来实现内存管理。但是如果有多个指针指向同一个内存,就存在多次释放的问题。于是有的语言就使用了引用计数的技术,每个指针变量都是内存的所有者。变量离开作用域的时候,只减少引用计数,并不立即执行释放内存的操作。待等到引用计数降为0的时候,才释放内存。Rust则从源头控制任意时刻只有一个所有者,这样所有者离开作用域,就能立即丢弃这个值。
变量与数据交互方式
那如何确保上述规则呢,这里就要提到变量与数据交互的几种方式:移动、克隆、引用(借用)。
移动是Rust语言有别于其他语言的一种行为。在C/C++中,我们多个指针变量可以指向同一块内存,可以对任意一个调用free或者delete。但是Rust中为了确保唯一所有者这个规则,当你用另一个变量指向当前变量指向的内存时,它认为你后续不会再使用前面这个变量了,也就是把所有权转移给另一个变量了。
如果你要保持当前指针的有效,其中的一个办法就是克隆一块新的内存,这样两块内存,两个所有者就没有违反上述规则了。对于堆内存而言,通常需要调用clone方法。对于栈内存而言,类似int之类的编译时已知大小的类型,拷贝往往是自动发生的。而一些特殊的自定义类型,需要实现Copy trait。
但是克隆大内存往往会产生比较大的消耗,而且往往我们并不需要一块新的内存。我们能不能在不转移所有权的情况下,用其他变量去借用一下这块内存?有,这就是引用。引用很好,既没有转移所有权,也没有增加所有者。但是引用引入了新的问题。
引用
数据竞争
引用给了我们用其他变量访问同一块内存的能力,而且不会转移所有权,这很棒,但是默认情况下,引用只能读取值,而不能修改值。除非你声明一个可变引用:
fn main() {let mut s = String::from("hello");change(&mut s);
}fn change(some_string: &mut String) {some_string.push_str(", world");
}
现在有了能读能写的引用,看起来一切很美好,但是这就引入了读写竞争问题。
数据竞争(data race)类似于竞态条件,它可由这三个行为造成:
- 两个或更多指针同时访问同一数据。
- 至少有一个指针被用来写入数据。
- 没有同步数据访问的机制。
不可变引用的用户可不希望在他们的眼皮底下值就被意外的改变了!然而,多个不可变引用是可以的,因为没有哪个只能读取数据的人有能力影响其他人读取到的数据。所以两个可变引用、一个可变引用多个不可变引用都不能在生命周期内发生重叠,否则编译器会报错。
悬垂引用
在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针(dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。Rust通过检查所有者的生命周期,来确保引用的有效性。
生命周期注解
生命周期的主要目标是避免悬垂引用,大部分时候生命周期是隐含并可以推断的,但也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明他们的关系,这样就能确保运行时实际使用的引用绝对是有效的。来看一个例子:
fn main() {let string1 = String::from("abcd");let string2 = "xyz";let result = longest(string1.as_str(), string2);println!("The longest string is {}", result);
}fn longest(x: &str, y: &str) -> &str {if x.len() > y.len() {x} else {y}
}
当我们定义这个函数的时候,并不知道传递给函数的具体值,所以也不知道到底是 if
还是 else
会被执行。我们也不知道传入的引用的具体生命周期,所以也就不能通过观察作用域来确定返回的引用是否总是有效。 为了修复这个错误,我们将增加泛型生命周期参数来定义引用间的关系以便借用检查器可以进行分析。
生命周期注解并不改变任何引用的生命周期的长短。相反它们描述了多个引用生命周期相互的关系,而不影响其生命周期。与当函数签名中指定了泛型类型参数后就可以接受任何类型一样,当指定了泛型生命周期后函数也能接受任何生命周期的引用。
生命周期注解有着一个不太常见的语法:生命周期参数名称必须以撇号('
)开头,其名称通常全是小写,类似于泛型其名称非常短。大多数人使用 'a
作为第一个生命周期注解。生命周期参数注解位于引用的 &
之后,并有一个空格来将引用类型与生命周期注解分隔开。
简而言之,通过生命周期注解,我们告诉Rust函数有哪些参数与其返回值的生命周期进行关联。函数只能在它们生命周期重叠区域使用。
部分引用-slice
前面我们讲的引用都是引用整体,对于一些序列型的数据类型,例如字符串和array,我们可以引用其部分数据,这种引用类型称为slice。字符串slice使用下标区间从字符串获得引用值:
fn main() {let s = String::from("hello world");let hello = &s[0..5];let world = &s[6..11];
}
字符串字面值的类型是 &str
:它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串字面值是不可变的;&str
是一个不可变引用。
我们也会想要引用数组的一部分。我们可以这样做:
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);
这个 slice 的类型是 &[i32]
。它跟字符串 slice 的工作方式一样,通过存储第一个集合元素的引用和一个集合总长度。你可以对其他所有集合使用这类 slice。
智能指针
智能指针(smart pointers)是一类数据结构,他们的表现类似指针,但是也拥有额外的元数据和功能。在 Rust 中普通引用和智能指针的一个区别是,引用是一类只借用数据的指针;相反,在大部分情况下,智能指针 拥有 他们指向的数据。
智能指针通常使用结构体实现。智能指针不同于结构体的地方在于其实现了 Deref
和 Drop
trait。Deref
trait 允许智能指针结构体实例表现的像引用一样,这样就可以编写既用于引用、又用于智能指针的代码。Drop
trait 允许我们自定义当智能指针离开作用域时运行的代码。
Box<T>
前面我们提到了,栈上的值不会被多个变量共享,只会产生多个拷贝。但有时候我们希望将值在堆上开辟。这里使我们不得不这么做的一个例子是递归类型。Box<T>使我们能够像指针变量一样访问值类型。
它们多用于如下场景:
- 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
- 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
- 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候
RC<T>
前面我们提到了Rust是单所有权的,如果想使用多所有权,就是要使用引用计数。Rust的Rc<T>类型可以实现这个功能。Rc<T>
只能用于单线程场景。可以通过RC::clone()来增加引用计数,Rc<T>
的引用计数叫strong_count。而Weak<T>的引用计数叫weak_count。
通过不可变引用, Rc<T>
允许在程序的多个部分之间只读地共享数据。
RefCell<T>
前面我们提到,借用规则规定在有不可变引用时,不可以改变数据。然而,特定情况下,令一个值在其方法内部能够修改自身,而在其他代码中仍视为不可变,是很有用的。值方法外部的代码就不能修改其值了。RefCell<T>
是一个获得内部可变性的方法。RefCell<T>
并没有完全绕开借用规则,编译器中的借用检查器允许内部可变性并相应地在运行时检查借用规则。如果违反了这些规则,会出现 panic 而不是编译错误。
当创建不可变和可变引用时,我们分别使用 &
和 &mut
语法。对于 RefCell<T>
来说,则是 borrow
和 borrow_mut
方法,这属于 RefCell<T>
安全 API 的一部分。borrow
方法返回 Ref<T>
类型的智能指针,borrow_mut
方法返回 RefMut<T>
类型的智能指针。这两个类型都实现了 Deref
,所以可以当作常规引用对待。
RefCell<T>
记录当前有多少个活动的 Ref<T>
和 RefMut<T>
智能指针。每次调用 borrow
,RefCell<T>
将活动的不可变借用计数加一。当 Ref<T>
值离开作用域时,不可变借用计数减一。就像编译时借用规则一样,RefCell<T>
在任何时候只允许有多个不可变借用或一个可变借用。
总结:相较于RC<T>的多所有权,RefCell<T>是单所有权的。它们都允许有多个引用,但是
RC<T>的引用都是不可变的,而RefCell<T>可以返回可变引用。Box<T>
允许在编译时执行不可变或可变借用检查;RefCell<T>
允许在运行时执行不可变或可变借用检查。
Weak<T>
在使用引用计数的语言里,都面临一个因循环引用导致引用计数不能减少到0,而造成内存泄露的问题。可以使用Weak<T>来解决该问题。
参考:
1.Rust 程序设计语言 简体中文版
相关文章:
浅谈Rust内存管理
Rust因在内存管理上的独到之处,近年来受到了不少开发者的青睐。Rust内存管理的核心功能就是所有权。不同的语言采取了不同的内存管理方式,主要分为开发者手动管理或者编译器辅助管理,以及垃圾回收机制等。Rust的所有权机制,有别于…...
Vue路由跳转至页面后多次渲染
在 Vue 中,当你跳转到一个新的路由或者重新加载当前路由时,由于 Vue Router 或其他路由管理工具的机制,会导致该页面组件重新渲染多次的情况发生。这可能是因为以下原因: 组件复用:Vue Router 默认情况下会尝试复用已经…...
CDH大数据平台集群部署
文章目录 1. 资源准备2. 部署 Mariadb 数据库3. 安装CM服务4. 安装数据节点5. 登录CM系统 1. 资源准备 准备好CDH安装包资源,官方网站下载需要账号,如果没有账号可以去网上到处搜搜。主要涉及到的资源有: cloudera-manager-servercloudera-m…...
基于springboot+vue的校园资产管理系统
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…...
@RequestMapping 注解使用技巧
一、RequestMapping 基础用法 用于将任意HTTP 请求映射到控制器方法上。 RequestMapping表示共享映射,如果没有指定请求方式,将接收GET、POST、HEAD、OPTIONS、PUT、PATCH、DELETE、TRACE、CONNECT所有的HTTP请求方式。GetMapping、PostMapping、PutMapp…...
AtCoder 265G 线段树
题意 传送门 AtCoder 265G 012 Inversion 题解 直接维护逆序对数量比较困难,考虑到元素值域很小,直接将不同数值对解耦进行维护。具体而言,线段树维护区间 0 , 1 , 2 0,1,2 0,1,2 的数量,以及满足 i < j i<j i<j 时…...
通俗易懂了解大语言模型LLM发展历程
1.大语言模型研究路程 NLP的发展阶段大致可以分为以下几个阶段: 词向量词嵌入embedding句向量和全文向量理解上下文超大模型与模型统一 1.1词向量 将自然语言的词使用向量表示,一般构造词语字典,然后使用one-hot表示。 例如2个单词&…...
Vim - 快速插入C语言函数注释模板
背景 C语言使用vim编写时,需要快速对函数进行说明头插入; 代码 function! InsertCFunctionHeader()" 获取当前行内容let line getline(.)" 匹配 C 函数定义let matched matchlist(line, ^\s*\w\ \\(\w\\)(\(.*\)))" 如果当前行不是函…...
Leetcode171. Excel 表列序号
给你一个字符串 columnTitle ,表示 Excel 表格中的列名称。返回 该列名称对应的列序号 。 例如: A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ... 题解:力扣(LeetCode)官网 - 全球极客挚爱…...
自主设计,模拟实现 RabbitMQ - 实现 拒绝/否定 应答机制
目录 一、拒绝/否定 应答机制 1.1、需求分析 什么是 拒绝/否定 应答呢?...
在github上设置不同分支,方便回滚
在github上设置不同分支,方便回滚 步骤可能出现的问题couldnt find remote ref gpuVersion1. 确保您处于正确的分支2. 添加并提交更改(如果还未进行)3. 推送本地分支到远程仓库4. 验证操作 步骤 之前在github上上传了一个项目代码,…...
【Elsevier旗下】JCR2/3区,最快25天录用!计算机与娱乐、教育、游戏、新媒体均可
期刊简介: 出版社:Elsevier 影响因子(2022):2.5-3.0 期刊分区:JCR2/3区,中科院4区 检索数据库:SCIE 在检 数据库检索年份:2016年 预警情况:无中科院预警…...
TSINGSEE视频AI智能分析技术:水泥厂安全生产智能监管解决方案
一、方案背景 随着人工智能技术的快速发展以及视频监控系统在全国范围内的迅速推进,基于AI视频智能分析技术的智能视频监控与智慧监管系统,也已经成为当前行业的发展趋势。在工业制造与工业生产领域,工厂对设备的巡检管理、维护维修、资产管…...
Whisper + NemoASR + ChatGPT 实现语言转文字、说话人识别、内容总结等功能
引言 2023年,IT领域的焦点无疑是ChatGPT,然而,同属OpenAI的开源产品Whisper似乎鲜少引起足够的注意。 Whisper是一款自动语音识别系统,可以识别来自99种不同语言的语音并将其转录为文字。 如果说ChatGPT为计算机赋予了大脑&…...
795. 区间子数组个数
795. 区间子数组个数 给你一个整数数组 nums 和两个整数:left 及 right 。找出 nums 中连续、非空且其中最大元素在范围 [left, right] 内的子数组,并返回满足条件的子数组的个数。 生成的测试用例保证结果符合 32-bit 整数范围。 示例 1:…...
Request method ‘GET‘ not supported,不支持GET形式访问
org.springframework.web.HttpRequestMethodNotSupportedException: Request method ‘GET’ not supported 原因:异常提示的很明确,请求不支持GET方式访问,出现这种问题一般都是由于限制请求接口为POST,然后使用GET形式访问造成的…...
数据结构与算法(C语言版)P2---线性表之顺序表
前景回顾 #mermaid-svg-sXTObkmwPR34tOT4 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-sXTObkmwPR34tOT4 .error-icon{fill:#552222;}#mermaid-svg-sXTObkmwPR34tOT4 .error-text{fill:#552222;stroke:#552222;}#…...
AI写文章软件-怎么选择不同的AI写文章软件
在如今信息爆炸的时代,无论是学生、职场人士,还是创作者和企业家,写文章都是一项常见而又重要的任务。然而,随着科技的不断进步,AI写文章的软件也逐渐走进了人们的视野。 147GPT批量文章生成工具www.147seo.com/post…...
VSCode远程连接服务器报错:Could not establish connection to
参考:https://blog.csdn.net/weixin_42538848/article/details/118113262 https://www.jb51.net/article/219138.htm 刚开始把ssh文件夹中的known_hosts给删除了,发现没啥用。 之后在扩展Remote-SSH里面,把config file路径设置为ssh文件夹里…...
openssl 用法整理 —— 筑梦之路
用法一 生成自签名数字证书 # 生成私钥 openssl genpkey -algorithm RSA -out private.key# 生成证书请求 openssl req -new -key private.key -out certificate.csr# 使用私钥签署证书 openssl x509 -req -days 365 -in certificate.csr -signkey private.key -out certifica…...
Mac安装SPSS 26(含安装包)
Mac安装SPSS 26(含安装包) 安装包地址(百度网盘):https://pan.baidu.com/s/127ZJNRIMZaeR2hDilQT0Zg提取码: m5xj 查看是否允许安装任何来源的app 如果没有任何来源这个选项 打开终端输入:sudo spctl --master-disable回车之后输入password(注:电脑的…...
uniapp存值和取值方法
在UniApp中,可以使用全局变量、本地缓存和Vuex状态管理等方式来进行存值和取值。 全局变量:可以在App.vue文件的data中定义一个全局变量,在其他页面或组件中通过uni.$emit方法修改其值,并通过uni.$on方法监听值的变化。 // App.…...
Apache Beam 2.50.0发布,该版本包括改进功能和新功能
导读我们很高兴向您介绍 Beam 的新版本 2.50.0。该版本包括改进功能和新功能。请查看此版本的下载页面。 亮点 Spark 3.2.2 被用作 Spark 运行程序的默认版本(#23804)。Go SDK 新增默认本地运行程序,名为 Prism(#24789࿰…...
华为云云耀云服务器 L 实例评测|配置教程 + 用 Python 简单绘图
文章目录 Part.I IntroductionChap.I 云耀云服务器 L 实例简介Chap.II 参与活动步骤 Part.II 配置Chap.I 初步配置Chap.II 配置安全组 Part.III 简单使用Chap.I VScode 远程连接华为云Chap.II 简单绘图 Reference Part.I Introduction 本篇博文是为了参与华为“【有奖征文】华…...
栈的简单应用(利用Stack进行四则混合运算)(JAVA)
目录 中缀表达式转后缀表达式 图解 代码实现过程: 完整代码: 利用后缀表达式求值: 完整代码: 首先我们得先了解逆波兰表达式。 中缀表达式转后缀表达式 所谓的中缀表达式其实就是我们平时写的例如:࿱…...
Python---异常
捕获全部异常 语法: try: 可能发生的错误代码 except: 如果出现异常执行的代码 例子: try:open("test2.txt", "r", encoding"UTF-8") except:print("出现异常,文件不存在,换个模式打…...
视频编解码器H.264和H265有什么区别?
对于大型视频文件来说,视频编解码器至关重要,它可以将文件压缩为较小的尺寸,从而可以更轻松地存储和加快传输速度。而两种最常用的编解码器是H.264和H.265,那么它们两者之间有什么区别,哪一个更好呢? 1. 什…...
网络安全进阶学习第十六课——业务逻辑漏洞介绍
文章目录 一、什么是业务逻辑二、业务逻辑漏洞的成因三、逻辑漏洞的重要性四、业务逻辑漏洞分类五、业务逻辑漏洞——业务授权安全1、未授权访问2、越权访问1) 平行越权(水平越权是指相同权限的不同用户可以互相访问)2) 垂直越权(垂直越权是指…...
华为OD:跳房子I
题目描述 跳房子,也叫跳飞机,是一种世界性的儿童游戏。 游戏参与者需要分多个回合按顺序跳到第1格直到房子的最后一格 跳房子的过程中,可以向前跳,也可以向后跳。 假设房子的总格数是count,小红每回合可能连续跳的…...
C语言自定义类型详解(1)结构体知识汇总
本篇概要 本篇主要讲述C语言结构体的相关知识,包括结构体的基本声明,结构体的匿名结构,结构体的自引用,结构体变量的定义和初始化以及结构体的内存对齐等相关知识。 文章目录 本篇概要1.结构体1.1结构体的基本声明1.2结构体的特殊…...
北大青鸟培训/优化大师有必要安装吗
在Esri 2016年全球用户大会上,现场通过ArcGIS Pro、Web及ArcGIS Earth从不同的角度展示了丰富而广泛的3D应用,3D的使用无处不在。接下来会大家奉上精彩的内容!ArcGIS Pro ArcGIS Pro ArcGIS Pro支持点云、建筑三维模型、小品模型等多种类型…...
寻找南京帮助做网站的单位/南昌百度推广公司
在我们为 IDEA 等编辑器配置 svn 时,经常需要配置 svn.exe 文件的目录 C:\Program Files\TortoiseSVN\bin\svn.exe ; 但打开 svn.exe 文件的安装目录 C:\Program Files\TortoiseSVN\bin,却发现没有 svn.exe 文件,其原因是由于安…...
电大网上作业代做网站/百度竞价推广收费
作为一种定期清理无效数据的重要机制,主键失效存在于大多数缓存系统中,Reids也不例外。在Redis提供的诸多命令中,EXPIRE、EXPIREAT、PEXPIRE、PEXPIREAT以及SETEX和PSETEX均可以用来设置一条Key-Value对的失效时间,而一条Key-Valu…...
政府单位网站建设改版方案/西安 做网站
golang 模板(template)的常用基本语法 模板 在写动态页面的网站的时候,我们常常将不变的部分提出成为模板,可变部分通过后端程序的渲染来生成动态网页,golang提供了html/template包来支持模板渲染。 这篇文章不讨论golang后端的模板读取及渲染…...
新网站不被收录/电商网站大全
文章目录React 简介React 概述及特点React 虚拟DOMReact 渲染机制React 基本使用JSX 语法createElement()方法深究JSX语法规则React 简介 React 概述及特点 是一个用于构建用户界面,将数据渲染为HTML视图的开源 JavaScript库 中文官网:React官方中文文档…...
yii2 网站开发/软件推广平台
《计算机导论》教学大纲一、课程概述1. 课程研究对象和研究内容计算机导论对计算机科学的核心内容作概括介绍;使学生深刻理解计算机系统的信息处理功能本质;掌握数据表达和数据加工表达的层次方法;了解计算机系统的功能组成;认识计…...