前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rust 学习(前置:一)

Rust 学习(前置:一)

作者头像
小石头
发布2022-11-10 21:38:46
5900
发布2022-11-10 21:38:46
举报
文章被收录于专栏:小石头小石头小石头

学习Rust 前置步骤,别着急,我们不会直接开始介绍语法,而会先来回顾那些你平时认为非常基础的知识,比如说内存、函数。

编程语言万变不离其宗,底层逻辑都是不变的。

所以今天我们得回头重新思考,编程中那些耳熟能详却又似懂非懂的基础概念,搞清楚底层逻辑。而且这些概念,对我们后面学习和理解 Rust 中的知识点非常重要,之后,我们也会根据需要再穿插深入讲解。

代码中最基本的概念是变量和值,而存放它们的地方是内存,所以我们就从内存开始。

内存:

先来rust 的hello world ,查看这一句“hello world” 对应的内存使用情况

let s = "hello world".to_string();

首先,“hello world” 作为一个字符串常量(string literal),在编译时被存入可执行文件的 .RODATA 段(GCC)或者 .RDATA 段(VC++),然后在程序加载时,获得一个固定的内存地址。

当“hello world” .to_string() 的时候,在堆上,一块新的内存被分配出来,并吧 “hello world” 逐个字节拷贝过去。

当我们把堆上的数据赋值给 s 的时候,s 作为栈上的一个变量,需要知道堆上的内存的地址,由于堆上的数据大小不确定且可以增长,我们还需要知道size

最终,为了表述这个字符串,使用了三个word:

第一个,表示指针;

第二个, 表示字符串的当前长度;

第三个,表示这片内存的总容量。

内存变换流程

数据什么时候可以放在栈上,什么时候需啊哟放在堆上呢?

下面来复习一下堆栈的概念

我们使用java 会大概了解内存管理的一些规则

  • 基本类型(primitive type)存储在栈上,对象存储在堆上;
  • 少量数据存储在栈上,大量的数据存储在堆上。

这两个概念只是初略概念

我们深挖一下堆栈的设计原理:

栈是程序运行的基础。每当一个函数被调用时,一块连续的内存就会在栈顶被分配出来,这块内存被称为帧(frame)。

我们知道,栈是自顶向下增长的,一个程序的调用栈最底部,除去入口帧(entry frame),就是 main() 函数对应的帧,而随着 main() 函数一层层调用,栈会一层层扩展;调用结束,栈又会一层层回溯,把内存释放回去。

在调用的过程中,一个新的帧会分配足够的空间存储寄存器的上下文。在函数里使用到的通用寄存器会在栈保存一个副本,当这个函数调用结束,通过副本,可以恢复出原本的寄存器的上下文,就像什么都没有经历一样。此外,函数所需要使用到的局部变量,也都会在帧分配的时候被预留出来。

整个过程你可以再看看这张图辅助理解:

一个函数运行,怎么确定需要多大的帧呢?

这要归功于编译器。在编译并优化代码的时候,一个函数就是一个最小的编译单元。(需要生命周期标记的原因)

在这个函数里,编译器得知道要用到哪些寄存器、栈上要放哪些局部变量,而这些都要在编译时确定。所以编译器就需要明确每个局部变量的大小,以便于预留空间。

这下我们就明白了:在编译时,一切无法确定大小或者大小可以改变的数据,都无法安全地放在栈上,最好放在堆上。比如一个函数,参数是字符串:

fn say_name(name: String) {}

// 调用
say_name("Lindsey".to_string());
say_name("Rosie".to_string());

字符串的数据结构,在编译时大小不确定,运行时执行到具体的代码才知道大小。比如上面的代码,“Lindsey” 和 “Rosie” 的长度不一样,say_name() 函数只有在运行的时候,才知道参数的具体的长度。

所以,我们无法把字符串本身放在栈上,只能先将其放在堆上,然后在栈上分配对应的指针,引用堆上的内存。

放栈上的问题

从刚才的图中你也可以直观看到,栈上的内存分配是非常高效的。只需要改动栈指针(stack pointer),就可以预留相应的空间;把栈指针改动回来,预留的空间又会被释放掉。预留和释放只是动动寄存器,不涉及额外计算、不涉及系统调用,因而效率很高。

所以理论上说,只要可能,我们应该把变量分配到栈上,这样可以达到更好的运行速度。

那为什么在实际工作中,我们又要避免把大量的数据分配在栈上呢?

在实际调用的时候需要考虑到栈的大小,避免栈溢出(stack overflow),一旦当前程序的调用栈超出了系统允许的最大栈空间,无法创建新的帧,来运行下一个要执行的函数,就会发生栈溢出,这时程序会被系统终止,产生崩溃信息。

过大的栈内存分配是导致栈溢出的原因之一,更广为人知的原因是递归函数没有妥善终止。一个递归函数会不断调用自己,每次调用都会形成一个新的帧,如果递归函数无法终止,最终就会导致栈溢出。

堆上的问题

在使用堆内存的时候,也需要注意内存管理

如果使用手动管理内存空间的时候,需要及时释放内存,以免造成内存泄露,一旦内存泄露,没有及时回收内存,随着程序运行的时间越来越长,内存到最后会被吃满导致系统挂掉。

还有个要点:

假如堆内存被多个线程的调用栈引用,改动内存的时候要特别注意,需要加锁来独占访问,避免潜在问题,

线程很常见的问题比如:

一个线程在遍历List ,而另外一个线程在释放List 中的某一项,这时候就可能会访问野指针(野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。),导致堆越界(heap out of bounds)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-1-11 1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 放栈上的问题
  • 堆上的问题
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档