skip to content
Clifford Chen

Rust 变量和可变性

/ 8 min read

Table of Contents

参考书大纲

变量和可变性 - Rust 编程语言

  1. Rust 默认变量不可更改

  2. Rust 变量允许设置 可改 变量


疑问与解答

1. 为什么编程语言要设计变量?

变量是从自然语言抽象出来的符号,用来标识计算机地址。例如,数字计算程序

mov [0x7fff5000], 42 ; 把42存到这个地址
add [0x7fff5000], [0x7fff5008] ; 两个地址的值相加

    这段代码,可以是两个数字求和。人 将4242存到计算机内存地址0x7fff5000(像门牌号一样),然后与另一地址的数字求和。可人类记忆这样的地址串,太麻烦,不如给这些变量,一个符号特指。

    类比,我有一份材料放在12 Campbell Rd St Lucia QLD 4067 Australia,我的同学有一份材料放在42 Staff House Rd St Lucia QLD 4067 Australia ,现在我要合并两份材料,我告诉机器,请你将 xx campbell rd…xx Staff House Rd… 的两份材料合并,机器可以快速准确地理解地址。然而,人类觉得很麻烦,记不住这样长串,我只会这样告诉人类同学,请将 Central Library 和 Student Center的材料合并。

    这里,Central Library 和 Student Center就是人类声明的变量

    计算机中,变量给内存地址起了名字。例如rust代码let x: i32 = 42, 实际地址是0x7fff5000, 人类起的名字是x, 解释规则是i32, x 这个地方存着4242。 类比现实地址,let Central_Library: 搬运的是数字 = 42 实际地址是12 Campbell Rd St Lucia QLD 4067 Australia, 人类起个名字”Central Library”, 这里存着4242

    解释规则,现实类比,Central Library放着材料,这份材料,是一张纸,纸上有一串墨迹,这个墨迹,可以理解成是一个单纯的数字,一份学生作业答案,一份银行卡密码,某人朋友的生日…,不同的解释规则,得到完全不同的意义。计算机里也一样,内存地址 0x7fff5000 存着4个字节:01000010 00000000 00000000 00000000 (计算机用二进制存储数据,所有数据都是0 1数字组成的数字串),这4个字节是什么?取决于解释规则:

  • i32 读:整数 66
  • f32 读:一个完全不同的浮点数
  • 按 ASCII 读:字符 'B'

同一个地址,同样的内容,不同的解释规则,得到完全不同的意义。

所以 let x: i32 = 42 里,i32 不只是说”这里存着一个数字”,而是精确规定了:这4个字节要用二进制补码整数的方式来读

2. 变量声明的系统实现

2.1 认识CPU和内存

想象一个酒店:CPU 是酒店的前台和服务员,寄存器是服务员随身携带、可以改写的便签,内存是酒店的房间,内存地址就是房间号码。 为了先理解函数调用,我们暂时只关注内存中的两类区域:代码区和栈区。 代码区存储程序编译后的机器指令,比如 main 函数和 add 函数对应的指令。 栈区存储函数调用时产生的临时信息,比如局部变量、返回地址、旧 rbp 等。

这一节要理解

  • CPU 负责执行。

  • 寄存器负责临时记录关键值。

  • 内存负责存放代码和数据。

    • 代码区放指令。

    • 栈区服务函数调用。

2.2 一个例子看运行Rust后发生了什么

fn main() {
let a = 10;
add(a);
}
fn add(x: i32) -> i32 {
let b = 20;
x + b
}
  1. 机器指令写入内存代码区 0x1000 到 0x101c(是main函数部分的机器指令,写出来)… 0x3000到0x401c(是add指令,写出来)

2.3 Const变量怎么实现

2.4 shadowing 旧内存去哪了?

2.1. CPU和内存合作

CPU = 前台工作人员/跑腿的人 寄存器 = 工作人员手里的几张便签 内存 = 整栋酒店的房间 内存地址 = 房间号 程序指令 = 工作清单,也放在某些房间里 rip = 当前正在看的工作清单位置 rbp = 当前函数房间的基准房号 rsp = 当前栈房间使用到哪里了

下面代码为例:

fn main() {
let a = 10;
add(a);
}
fn add(x: i32) -> i32 {
let b = 20;
x + b
}

第一步:程序加载

main 的机器指令放进内存某处;add机器指令放进内存某处。

第二步:CPU执行main

    rip= main的第一条指令地址

前台服务员,拿到rip便签,上面写着“去0x1000房间拿下一条指令”,0x1000号房间(内存)里是的指令是进入main函数

第三步:main建立自己的栈帧

服务员找到一列空房间,拿出便条rbp,写下这列空房间的最高地址——main的基准地址push rbp,(这里push rbp吗)这时rip显示下一条指令的地址,店员过去一看,是声明变量a=10,于是店员将10投放在rbp-4房间,这样内存rbp-4位置有了a = 10。 再看rip,写着add函数的第一条指令地址,于是:

第四步:main调用add

店员再找到一列空房间,再次push rbp(再次push rbp吗), 这时rbp是main的基准地址,因而第一个(最高地址房间)存折rbp旧值,即main的首指令房间号。rbp便条擦除,写上当前的房号mov rbp, rsp rsp写着当前房号。

第五步:add执行代码

b=20

x+b

第六步:add结束

pop rdp, 擦除rdp便条的地址,(这里应该有返回值吧,店员应该回到main列房间了吧,add栈的内存怎么处理?没有回收啊) 回到main(怎么回到的main?)