【Rust中级教程】1.5. 内存 Pt.3:深入探究Rust堆内存底层实现

ops/2025/2/14 0:49:18/

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

1.5.1. 堆内存(Heap)

  • Heap意味着混乱,而stack则相对比较整齐。
  • Heap是一个内存池,并没有绑定到当前程序的调用栈,而stack绑定到当前程序的调用栈。
  • Heap是为在编译时没有已知大小的类型准备的,而stack上的数据在编译时大小必须已知。
    请添加图片描述

如图,heap上的数据存放的位置和自身的大小都是不一定的,比较常见的情况是stack有一个指针指向heap上的数据。

什么叫在编译时大小不已知?

  • 一些类型会随着时间变大或变小,例如StringVec<T>
  • 另一些类型的大小不会改变,但是无法告诉编译器需要分配多少内存
  • 另一个例子是trait对象(详见 【Rust自学】19.5.4. 动态大小和和Sized trait),它允许程序员模拟一些动态语言的特性——将多个类型放进一个容器
  • 以上三种都叫动态大小类型(dynamically sized types,简称DST)

Heap允许你显示地分配连续的内存块。当这么做时,你就会得到一个指针,它指向内存开始的地方。

Heap中的值会一直有效,直到你对它显式地释放。如果你想让值活得比当前函数frame(详见 1.4. 内存 Pt.2)的生命周期还长,就很有用。如果值是函数的返回值,那么调用函数可以在它的stack上留一些空间给被调用函数让它把值在返回前写入进去。

1.5.2. 堆内存与线程安全

如果想要把值送到另一个线程,当前线程可能根本无法与那个线程共享stack frames,你就可以把它存在堆内存上。因为函数返回时堆内存上的分配不会消失,所以你在一个地方为值分配内存,把指向它的指针传给另一个线程,就可以让那个线程安全地操作于这个值。

换一种说法:当你分配堆内存时,结果指针会有一个无约束的生命周期,你的程序想让数据活多久都行。

1.5.3. 堆内存的交互机制

堆内存上的变量必须通过指针访问。看个例子:

rust">fn main(){  let a: i32 = 40; // Stack  let b: Box<i32> = Box::new(60); // Heap  let result = a + b;  let result = a + *b;  println!("{} + {} = {}", a, b, result);  
}
  • ai32类型,放在栈内存上的
  • bBox<i32>类型,放在堆内存上的

但是这么写肯定是有问题的,问题出在let result = a + b;上:堆内存上的数据必须通过指针来访问,b是指针,a是数字,两者类型不同肯定不能相加。

所以我们删掉这句话把原代码改成:

rust">fn main(){  let a: i32 = 40; // Stack  let b: Box<i32> = Box::new(60); // Heap  let result = a + *b;  println!("{} + {} = {}", a, b, result);  
}

let result = a + *b;通过*b进行了解引用,把指针指向的值60取出来了。

输出:

40 + 60 = 100

Rust与堆内存的交互方式

Rust里与堆内存交互的主要方式就是Box<T>类型。

当我们使用Box::new函数创建类型为Box<T>的实例时,值(传进Box::new函数的参数)就会被放在堆内存上,返回的(BOx<T>)就是指向堆内存上的指针。当Box被丢弃时,内存就会被释放。

如果忘记释放堆内存,就会导致内存泄漏。但有时候程序员会故意让内存泄漏,例如有一个只读的配置,整个程序都需要访问它。这时候就可以通过Box::leak得到一个‘static引用,从而显式地让其进行泄露。

看个例子:

rust">use std::mem::drop;  fn main(){  let a = Box::new(1);  let b = Box::new(1);  let c = Box::new(1);  let result1 = *a + *b + *c;  drop(a);  let d = Box::new(1);  let result2 = *b + *c + *d;  println!("{} {}", result1, result2);  
}
  • 使用std::mem::drop函数可以手动释放

我们讲讲这个程序的逻辑:

  • 首先声明了变量abc,值都是存储在堆内存上的1(Box<i32>类型)
  • 使用*对三个变量都进行解引用然后相加得到result1
  • 得到result1之后使用drop函数直接抛弃了a
  • 然后声明了变量d,值是存储在堆内存上的1(Box<i32>类型)
  • 使用对*三个变量(bcd)进行解引用然后相加得到result2
  • 打印出result1result2

我们用一张图来看看这个程序执行中内存的变化:
请添加图片描述


http://www.ppmy.cn/ops/158179.html

相关文章

计算机网络和操作系统常见面试题目(带脑图,做了延伸以防面试官深入提问)

呜哦~~(✪▽✪)曼波~~~~ 今天我们来聊聊计算机网络和操作系统的面试题目吧&#xff01;这些题目是面试中经常遇到的&#xff0c;曼波觉得掌握它们对面试非常有帮助哦&#xff01;(๑✧◡✧๑) --- 1. 计算机网络面试题目 1.1 OSI 七层模型是什么&#xff1f; 回答&#xff…

智能背后的阴影:LLM安全风险

智能化与危机并行&#xff1a;解读LLM安全隐忧 ©作者|Lorne 来源|神州问学 一、背景 Background 图1&#xff1a;LLM潜在隐患 近年来&#xff0c;大语言模型&#xff08;Large Language Model, LLM&#xff09;研究的热度持续攀升。自ChatGPT问世并迅速走红以来&#xf…

数据结构——【二叉树模版】

#思路 1、二叉树不同于数的构建&#xff0c;在树节点类中&#xff0c;有数据&#xff0c;左子结点&#xff0c;右子节点三个属性&#xff0c;在树类的构造函数中&#xff0c;添加了变量maxNodes&#xff0c;用于后续列表索引的判断 2.GetTreeNode()函数是常用方法&#xff0c;…

NO.15十六届蓝桥杯备战|while循环|六道练习(C++)

while循环 while语法形式 while 语句的语法结构和 if 语句⾮常相似&#xff0c;但不同的是 while 是⽤来实现循环的&#xff0c; if 是⽆法实现循环的。 下⾯是 while 循环的语法形式&#xff1a; //形式1 while ( 表达式 )语句; //形式2 //如果循环体想包含更多的语句&a…

最新消息 | 德思特荣获中国创新创业大赛暨广州科技创新创业大赛三等奖!

2024年12月30日&#xff0c;广州市科技局公开第十三届中国创新创业大赛&#xff08;广东广州赛区&#xff09;暨2024年广州科技创新创业大赛决赛成绩及拟获奖企业名单&#xff0c;德思特获得了智能与新能源汽车初创组【第六名】【三等奖】的好成绩&#xff01; 关于德思特&…

Vim操作笔记

注&#xff1a;本篇文章是追加笔记&#xff0c;用于记录自己的常用操作。 将文本中A字符串替换成B字符串 基本语法&#xff1a; :{范围}s/{目标}/{替换}/{标志} 作用范围 分为前行(:s)、全文(:%s)、选区(:start,ends)等。选区可以在Visual模式下选择区域后输入&#xff1a…

大数据和数据科学——解锁数据潜力,驱动创新与洞察

在当今数字化时代&#xff0c;数据量呈爆炸式增长&#xff0c;大数据和数据科学已成为企业获取竞争优势、推动创新和实现业务转型的关键技术。《DAMA数据管理知识体系指南&#xff08;第二版&#xff09;》的第十四章深入探讨了大数据和数据科学的定义、业务驱动因素、活动、工…

在远程 Linux 服务器上运行 Jupyter Notebook(.ipynb 文件)

由于有的服务器没有浏览器&#xff0c;可以考虑通过 VScode 将远程服务器上的服务转发到本地计算机&#xff0c;然后在本地计算机的浏览器中运行远程服务器上的 ipynb 代码&#xff0c;实现交互式 Python 编程。。 安装 Jupyter&#xff1a; 如果没有安装 Jupyter&#xff0c;可…