Table of Contents
参考书大纲
-
Rust 默认变量不可更改
-
Rust 变量允许设置 可改 变量
疑问与解答
1. 为什么编程语言要设计变量?
变量是从自然语言抽象出来的符号,用来标识计算机地址。例如,数字计算程序
mov [0x7fff5000], 42 ; 把42存到这个地址add [0x7fff5000], [0x7fff5008] ; 两个地址的值相加 这段代码,可以是两个数字求和。人 将存到计算机内存地址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 这个地方存着。 类比现实地址,let Central_Library: 搬运的是数字 = 42 实际地址是12 Campbell Rd St Lucia QLD 4067 Australia, 人类起个名字”Central Library”, 这里存着。
解释规则,现实类比,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}-
机器指令写入内存代码区 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?)