Rust编程语言入门教程(八)所有权 Stack vs Heap

news2025/2/23 3:31:16

Rust 系列

🎀Rust编程语言入门教程(一)安装Rust🚪
🎀Rust编程语言入门教程(二)hello_world🚪
🎀Rust编程语言入门教程(三) Hello Cargo🚪
🎀Rust编程语言入门教程(四)猜数游戏:一次猜测🚪
🎀Rust编程语言入门教程(五)猜数游戏:生成、比较神秘数字并进行多次猜测🚪
🎀Rust编程语言入门教程 (六)变量与可变性🚪
🎀Rust编程语言入门教程 (七)函数与控制流🚪

目录

  • Rust 系列
  • 引言
  • 一、什么是所有权?
  • 二、Stack(栈内存) vs Heap (堆内存)
    • (一)访问数据
    • (二)函数调用
    • (三)所有权解决的问题
  • 三、所有权规则
  • 四、理解所有权规则(String 类型举例)
    • (一)创建 String 类型的值
    • (二)内存和分配
    • (三)变量和数据交互的方式:移动(Move)
      • ① 一个String由3部分组成:
      • ② 浅拷贝(shallow copy) 深拷贝(deep copy)
      • ⑤ 克隆(Clone)
    • (四)stack上的数据:复制
    • (五)一些拥有 Copy trait 的类型
  • 五、所有权与函数
    • 如何让函数使用某个值,但不获得其所有权?
  • 总结

引言

在现代编程语言中,内存管理和性能优化一直是开发者面临的挑战。许多语言依赖垃圾回收机制(GC)来自动管理内存,但这种方式可能会引入运行时开销。另一些语言则要求程序员手动管理内存,这虽然提供了更高的性能,但也容易导致内存泄漏、悬空指针等问题。Rust 通过其独特的所有权系统,提供了一种无需垃圾回收即可保证内存安全的解决方案。所有权机制通过一系列编译时规则,确保程序在运行时不会出现内存泄漏或数据竞争等问题。本文将深入探讨 Rust 的所有权机制,包括栈(Stack)和堆(Heap)内存的管理,以及如何通过移动(Move)、克隆(Clone)和复制(Copy)等操作安全地处理数据。

一、什么是所有权?

所有权是 Rust 最独特的特性,它让Rust无需GC(垃圾回收)就可以保证内存安全。
Rust的核心特性就是所有权

所有程序在运行时都必须管理它们使用计算机内存的方式。
有些语言有垃圾收集机制,在程序运行时,它们会不断地寻找不再使用的内存。比如C#、Java。
在其他语言中,程序员必须显式地分配和释放内存。比如 C、C++。

Rust采用了第三种方式:
内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则。
当程序运行时,所有权特性不会减慢程序的运行速度。

二、Stack(栈内存) vs Heap (堆内存)

在像 Rust 这样的系统级编程语言里,一个值是在 stack 上还是在 heap 上对语言的行为和你为什么要做某些决定是有更大的影响的。

在你的代码运行的时候,Stack 和 Heap 都是你可用的内存,但他们的结构很不相同。

Stack值的接收顺序来存储,按相反的顺序将它们移除(后进先出,LIFO)

  • 添加数据叫做压入栈
  • 移除数据叫做弹出栈

所有存储在 Stack 上的数据必须拥有已知的固定的大小
编译时大小未知的数据或运行时大小可能发生变化的数据必须存放在 heap上。

Heap 内存组织性差一些:

  • 当你把数据放入 heap时,你会请求一定数量的空间。
  • 操作系统在 heap 里找到一块足够大的空间,把它标记为在用,并返回一个指针,也就是这个空间的地址
  • 这个过程叫做在 heap 上进行分配,有时仅仅称为“分配。

把值压到 stack 上不叫分配。因为指针是已知固定大小的,可以把指针存放在 stack上。但如果想要实际数据,你必须使用指针来定位

把数据压到 stack上要比在 heap 上分配快得多。因为操作系统不需要寻找用来存储新数据的空间,那个位置永远都在 stack 的顶端。在 heap 上分配空间需要做更多的工作: 操作系统首先需要找到一个足够大的空间来存放数据,然后要做好记录方便下次分配。

(一)访问数据

访问 heap 中的数据要比访问 stack 中的数据慢,因为需要通过指针才能找到 heap 中的数据。
对于现代的处理器来说,由于缓存的缘故,如果指令在内存中跳转的次数越少,那么速度就越快

  • 如果数据存放的距离比较近,那么处理器的处理速度就会更快一些 (stack上)
  • 如果数据之间的距离比较远,那么处理速度就会慢一些 (heap上)
  • 在 heap 上分配大量的空间也是需要时间的。

(二)函数调用

当你的代码调用函数时,值被传入到函数 (也包括指向 heap的指针)。函数本地的变量被压到 stack上。当函数结束后,这些值会从stack上弹出。

(三)所有权解决的问题

  • 跟踪代码的哪些部分正在使用 heap 的哪些数据
  • 最小化 heap 上的重复数据量
  • 清理 heap 上未使用的数据以避免空间不足,
    一旦你懂的了所有权,那么就不需要经常去想 stack 或 heap 了。但是知道管理 heap 数据是所有权存在的原因,这有助于解释它为什么会这样工作。

三、所有权规则

  • 每个值都有一个变量,这个变量是该值的所有者。
  • 每个值同时只能有一个所有者。
  • 当所有者超出作用域(scope)时,该值将被删除。

在这里插入图片描述

四、理解所有权规则(String 类型举例)

String 比那些基础标量数据类型更复杂。
字符串字面值:程序里手写的那些字符串值。它们是不可变的。
Rust 还有第二种字符串类型:String。在 heap 上分配。能够存储在编译时未知数量的文本。

(一)创建 String 类型的值

  • 可以使用 from 函数从字符串字面值创建出 String 类型
    let s = String::from(“hello”);
    “ :: ” 表示 from 是 String 类型下的函数。
  • 这类字符串是可以被修改的。
  • 为什么 String 类型的值可以修改,而字符串字面值却不能修改,因为它们处理内存的方式不同

在这里插入图片描述

(二)内存和分配

字符串字面值,在编译时就知道它的内容了,其文本内容直接被硬编码到最终的可执行文件里。速度快、高效,是因为其不可变性。
String 类型,为了支持可变性,需要在 heap 上分配内存来保存编译时未知的文本内容:

  • 操作系统必须在运行时来请求内存,这步通过调用 String::from 来实现
  • 当用完String之后,需要使用某种方式将内存返回给操作系统。这步,在拥有GC的语言中,GC会跟踪并清理不再使用的内存

没有GC,就需要我们去识别内存何时不再使用,并调用代码将它返回
如果忘了,那就浪费内存;
如果提前做了,变是就会非法;
如果做了两次,也是Bug。必须一次分配对应一次释放。

Rust采用了不同的方式:对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动的交还给操作系统。会调用 drop函数。

(三)变量和数据交互的方式:移动(Move)

多个变量可以与同一个数据使用一种独特的方式来交互。
let x= 5;
let y = X;

整数是已知且固定大小的简单的值,这两个5被压到了stack中。

String版本

let s1 = String::from(“hello”);
let s2 = s1;

① 一个String由3部分组成:

  • 指向存放字符串内容的内存的指针
  • 长度
  • 容量
    上面这些东西放在 stack 上。
    存放字符串内容的部分在 heap上
    长度 len,就是存放字符串内容所需的字节数。
    容量 capacity 是指 String 从操作系统总共获得内存的总字节数。
    在这里插入图片描述

let s1 = String::from(“hello”);
let s2 = s1;

当把 s1 赋给 s2,String 的数据被复制了一份
在 stack上复制了一份指针、长度、容量,并没有复制指针所指向的 heap上的数据。

当变量离开作用域时,Rust 会自动调用 drop 函数,并将变量使用的 heap 内存释放
当s1、s2离开作用域时,它们都会尝试释放相同的内存:
二次释放(double free) bug

在这里插入图片描述

为了保证内存安全:
Rust 没有尝试复制被分配的内存。
Rust 让 s1 失效。当 s1 离开作用域的时候,Rust 不需要释放任何东西。
试试看当 s2 创建以后再使用 s1是什么效果。
在这里插入图片描述
cargo run 运行,会发现报错。借用了已经移动的s1。
在这里插入图片描述

② 浅拷贝(shallow copy) 深拷贝(deep copy)

你也许会将复制指针、长度、容量视为浅拷贝,但由于 Rust 让 s1失效了,所以我们用一个新的术语: 移动(Move)
隐含的一个设计原则: Rust不会自动创建数据的深拷贝。就运行时性能而言,任何自动赋值的操作都是廉价的。

s1 赋值给 s2 之后就失效了。
在这里插入图片描述

⑤ 克隆(Clone)

如果真想对 heap 上面的 String 数据进行深度拷贝,而不仅仅是 stack上的数据,可以使用clone方法。主要针对堆上面的数据。
在这里插入图片描述

在这里插入图片描述

(四)stack上的数据:复制

Copy trait,可以把trait 简单理解为接口。可以用于像整数这样完全存放在 stack 上面的类型。
如果一个类型实现了Copy 这个 trait,那么旧的变量在赋值后仍然可用。
如果一个类型或者该类型的一部分实现了Drop trait,那么Rust 不允许让它再去实现 Copy trait 了。

x 是整数类型,在编译的时候就已经确定了它的大小,并且能将数据完整地存储在stack 中。而对这些数的复制操作,都是非常快速的。在创建变量y之后,我们没有任何理由阻止变量x继续保持有效。换句话而言,对于这些类型,深拷贝和浅拷贝没有区别的。调用Clone()方法不会与直接的浅拷贝有任何行为上的区别。
在这里插入图片描述

(五)一些拥有 Copy trait 的类型

  • 任何简单标量的组合类型可以是 Copy 的;
  • 任何需要分配内存某种资源都不是 Copy 的;

一些拥有 Copy trait 的类型:

  • 所有的整数类型,例如U32;
  • bool
  • char
  • 所有的浮点类型,例如f64
  • Tuple(元组),如果其所有的字段都是Copy的
    ( i32, i32) 是
    ( i32, String) 不是

五、所有权与函数

在语义上,将值传递给函数把值赋给变量是类似的:将值传递给函数将发生移动或复制。

函数在返回值的过程中同样也会发生所有权的转移

一个变量的所有权总是遵循同样的模式:

  • 把一个值赋给其它变量时就会发生移动
  • 当一个包含 heap 数据的变量离开作用域时,它的值就会被 drop 函数清理,除非数据的所有权移动到另一个变量上了。
    在这里插入图片描述

如何让函数使用某个值,但不获得其所有权?

Rust 有一个特性叫做 引用 (Reference) 这个我们下一节再讲!

总结

Rust 的所有权系统是其最核心的特性之一,它通过一系列严格的编译时规则,确保了内存的安全性和高效性。通过栈和堆的管理,Rust 能够在运行时快速分配和释放内存,同时避免了垃圾回收机制带来的性能开销。所有权规则确保每个值在任何时刻只有一个所有者,并在所有者超出作用域时自动清理内存。通过移动(Move)、克隆(Clone)和复制(Copy)等机制,Rust 提供了灵活且高效的内存管理方式。这些特性不仅使得 Rust 成为一种适合系统编程的语言,也为开发者提供了一种安全、高效的编程范式。掌握所有权机制,是深入理解 Rust 编程语言的关键一步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2303730.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

交易所开发:数字市场的核心动力

数字资产交易所作为连接用户与市场的核心枢纽,已成为推动数字经济发展的关键引擎。其开发不仅需要技术创新,还需兼顾用户体验、合规安全与生态构建,以下是交易所开发的核心要素与实践路径分析: 一、交易所的核心定位与技术架构…

Jmeter进阶篇(34)如何解决jmeter.save.saveservice.timestamp_format=ms报错?

问题描述 今天使用Jmeter完成压测执行,然后使用命令将jtl文件转换成html报告时,遇到了报错! 大致就是说jmeter里定义了一个jmeter.save.saveservice.timestamp_format=ms的时间格式,但是jtl文件中的时间格式不是标准的这个ms格式,导致无法正常解析。对于这个问题,有如下…

Navicat17详细安装教程(附最新版本安装包和补丁)2025最详细图文教程安装手册

目录 前言:为什么选择Navicat 17? 一、下载Navicat17安装包 二、安装Navicat 1.运行安装程序 2.启动安装 3.同意“协议” 4.设置安装位置 5.创建桌面图标 6.开始安装 7.安装完成 三、安装补丁 1.解押补丁包 2.在解压后的补丁包目录下找到“w…

一文详解U盘启动Legacy/UEFI方式以及GPT/MBR关系

对于装系统的老手而说一直想研究一下装系统的原理,以及面对一些问题时的解决思路,故对以前的方法进行原理上的解释,主要想理解其底层原理。 引导模式 MBR分区可以同时支持UEFI和Legacy引导,我们可以看一下微pe制作的启动盘&#…

LeetCode 热题 100_搜索二维矩阵(64_74_中等_C++)(二分查找)(暴力破解法;Z字形查找;一次二分查找)

LeetCode 热题 100_搜索二维矩阵(64_74) 题目描述:输入输出样例:题解:解题思路:思路一(暴力破解法):思路二(Z字形查找):思路三&#x…

学习量化交易的环境安装记录

1、安装anaconda 因为使用python,需要安装anaconda,具体是下面的官方地址,根据自己需要下载相应的版本 https://www.anaconda.com/download 运行上面下载的文件,安装anaconda 可以根据自己需要安装到相应的盘上面 同时环境变量…

MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 高级篇 part 1

第01章_Linux下MySQL的安装与使用 首先在vmware中下载centos7,实际上8更好一点,不过centos已经是时代的眼泪了,我之前已经教过了,不过是忘了,所以重新说一遍,看文档即可 2.开机前修改mac地址 &#xff0…

基于AVue的二次封装:快速构建后台管理系统的CRUD方案

基于AVue的二次封装:快速构建后台管理系统的CRUD方案 在开发后台管理系统时,表格是常见的组件之一。然而,使用原生的Element Plus实现CRUD(增删改查)功能往往需要编写大量重复代码,过程繁琐。即使借助类似…

第6章:基于LangChain如何开发Agents,附带客户支持智能体示例

本文主要介绍了 LangChain4j 中的 Agent(代理) 概念,以及如何使用 LangChain4j 构建代理系统,重点提供了一个客户支持系统的智能体样例 代理(Agents)| LangChain4j 注意: 请注意,“A…

传统的自动化行业的触摸屏和上位机,PLC是否会被取代?

传统的自动化行业的触摸屏和上位机是否会被取代? 在工业自动化领域,触摸屏和上位机长期扮演着核心角色,尤其在污水处理、化工生产等场景中,它们通过实时数据采集、逻辑控制、报警联动等功能,保障了生产设备的稳定运行…

智能合约的部署

https://blog.csdn.net/qq_40261606/article/details/123249473 编译 点击图中的 “Compile 1_Storage.sol” 存和取一个数的合约&#xff0c;remix自带 pragma solidity >0.8.2 <0.9.0; /*** title Storage* dev Store & retrieve value in a variable* custom:d…

word$deepseep

1、进入官网地址。 DeepSeek 2、进入DeepSeek的API文档 3、点击DeepSeek开放平台左侧的“API Keys”, 再点击“创建API Key” 4、在弹出的对话框中&#xff0c;输入自己的API Key名称&#xff0c;点击创建。 sk-0385cad5e19346a0a4ac8b7f0d7be428 5、打开Word文档。 6、Word找…

Mac系统下使用Docker快速部署MaxKB:打造本地知识库问答系统

随着大语言模型的广泛应用&#xff0c;知识库问答系统逐渐成为提升工作效率和个人学习的有力工具。MaxKB是一款基于LLM&#xff08;Large Language Model&#xff09;大语言模型的知识库问答系统&#xff0c;支持多模型对接、文档上传和自动爬取等功能。本文将详细介绍如何在Ma…

如何为自己的 PDF 文件添加密码?在线加密 PDF 文件其实更简单

随着信息泄露和数据安全问题的日益突出&#xff0c;保护敏感信息变得尤为重要。加密 PDF 文件是一种有效的手段&#xff0c;可以确保只有授权用户才能访问或修改文档内容。本文将详细介绍如何使用 CleverPDF 在线工具为你的 PDF 文件添加密码保护&#xff0c;确保其安全性。 为…

华为昇腾910b服务器部署DeepSeek翻车现场

最近到祸一台HUAWEI Kunpeng 920 5250&#xff0c;先看看配置。之前是部署的讯飞大模型&#xff0c;发现资源利用率太低了。把5台减少到3台&#xff0c;就出了他 硬件配置信息 基本硬件信息 按照惯例先来看看配置。一共3块盘&#xff0c;500G的系统盘&#xff0c; 2块3T固态…

hive—常用的函数整理

1、size(split(...))函数用于计算分割后字符串数组的长度 实例1&#xff09;&#xff1a;由客户编号列表计算客户编号个数 --数据准备 with tmp_test01 as ( select tag074445270 tag_id,202501busi_mon , 012399931003,012399931000 index_val union all select tag07444527…

Unity Mirror 多房间匹配

文章目录 一 、一些唠叨二 、案例位置三、多房间匹配代码解析四、关于MatchInterestManagement五、总结 一 、一些唠叨 最近使用Mirror开发了一款多人同时在线的肉鸽塔防游戏,其目的是巩固一下Mirror这个插件的熟练度,另一方面是想和身边的朋友一起玩一下自己开发的游戏. 但是…

基于flask+vue框架的的医院预约挂号系统i1616(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能:用户,医生,科室信息,就诊信息,医院概况,挂号信息,诊断信息,取消挂号 开题报告内容 基于FlaskVue框架的医院预约挂号系统开题报告 一、研究背景与意义 随着医疗技术的不断进步和人们健康意识的日益增强&#xff0c;医院就诊量逐年增加。传统的现场…

Rust编程语言入门教程(五)猜数游戏:生成、比较神秘数字并进行多次猜测

Rust 系列 &#x1f380;Rust编程语言入门教程&#xff08;一&#xff09;安装Rust&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;二&#xff09;hello_world&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;三&#xff09; Hello Cargo&#x1f…

【代码审计】-Tenda AC 18 v15.03.05.05 /goform接口文档漏洞挖掘

路由器&#xff1a;Tenda AC 18 v15.03.05.05 固件下载地址&#xff1a;https://www.tenda.com.cn/material?keywordac18 1./goform/SetSpeedWan 接口文档&#xff1a; formSetSpeedWan函数中speed_di参数缓冲区溢出漏洞&#xff1a; 使用 binwalk -eM 解包固件&#xff0c…