Page 63
我们已经学习了如何写一些Rust代码了.但是能写Rust代码和能写好的Rust代码又很大区别.
这一章讲述一些相对独立的指南,告诉你如何把的Rust代码更进一步.一些常用的模式和标准库会被提到.你可以随意安排阅读本章的顺序.
4.1 栈和堆
作为一个系统级别的语言,Rust可以在底层进行操作.如果你来自于高层语言,有一些系统编程概念你可能不太熟悉.最重要的就是内存中的栈和堆是如何工作的.如果你了解类C语言中栈的分配,这一章就是一个回顾.如果你不了解,你可以在本章学习这些概念了,但是我们关注在rust语言上.
4.1.1 内存管理
这里有两个概念是关于内存管理的.栈和堆是抽象的概念,可以帮助你决定何时分配内存或释放内存.
这里有一个高层次的对比:
栈是Rust默认的分配内存的地方,它的速度很快.但是这是在函数体中分配的内存,并且大小有限.另一方面,堆的速度慢一点,需要你在程序显示分配.但是到校是无限制的而且是全局可访问的.
Page 64
我们来讨论一下这个Rust程序:
fn main() {
let x = 42;
}
这个程序有一个变量绑定,x.需要在某处分配内存来存放它.Rust默认使用栈来分配内存,也就是说基本类型分配在栈上.这是什么意思呢?
当一个函数被调用后,一些内存被开辟出来用以保存这个函数的所有局部变量和其他一些东西.这称为"栈帧",在本书中我们忽略其他一些额外的信息,只考虑局部变量.在此例中,当main()函数运行时,我们分配了一个32位整型值在我们的栈帧中.这是自动实现的,我们并没有写什么Rust代码来实现这一功能.
当函数结束的时候,它的栈帧将要被释放.这也是自动完成的,我们没有做什么特殊的工作.
这个简单的例子就这样结束了.关键点是你需要理解栈的分配是相当相当快的.如果我们可以提前知道我们所有的局部变量,我们就可以对内存来一次总揽.因为我们可以一次就把所有的局部变量释放了,我们也就可以很快的释放这些内存.
缺点是我们不能在函数结束以后再保持这些局部变量了.我们还没有讨论栈这个字的含义呢.为此,我们需要一个稍微复杂一点的例子:
fn foo() {
let y = 5;
let z = 100;
}
fn main() {
let x = 42;
foo();
}
这个程序中含有3个局部变量:2个在foo(),1个在main().像之前的例子一样,当main()函数调用的时候,一个整型被分配在了栈上.但是当我们展示foo()调用的时候发生了什么,我们需要先把内存画出来.在你的操作系统看来内存是一个很简单的东西:一个很大的地址链表,从0开始到一个很大的数,就是你的内存能够容纳的大小.例如,如果你的内存有1G大小,你的地址从0到1,073,741,824.这个数来自于230,也就是1GB内存所包含的字节数.
Page 65
这个内存就像一个巨大的数组:地址从0开始然后增大到最大值.所以这一幅图可以表示我们的第一个栈帧;
地址 | 名字 | 值 |
0 | x | 42 |
我们在地址0的地方分配了一个叫做x的变量,它的值是42.
当foo()函数调用的时候,一个新的栈帧被分配了:
地址 | 名字 | 值 |
2 | z | 100 |
1 | y | 5 |
0 | x | 42 |
因为0是第一个栈帧,1和2就用于foo()函数的栈帧.当更多的函数被调用的时候,它向上增长.
这个有很重要的事我们先记录一下.数字0,1,3只是为了抽象说明,实际系统中的内存地址和这些数字没有关系.特别的,真实世界的内存地址是被一些字节分开的,这些分开的地址可能比它存放的数要大.
当foo()函数结束的时候,栈帧被释放了:
地址 | 名字 | 值 |
0 | x | 42 |
然后,当main()函数结束的时候,这最后一个值也被释放.简单吧!
栈之所被称为栈,是因为它工作起来就像盘子一样:第一个被放下的盘子在最后才能被拿起来.栈通常被称为"先入后出".你放入的最后一个值会最先被取出来.
让我们是一个3重深度的例子:
fn bar() {
let i = 6;
}
fn foo() {
let a = 5;
let b = 100;
let c = 1;
bar();
}
fn main() {
let x = 42;
foo();
}
ok,首先我们调用main():
地址 | 名字 | 值 |
0 | x | 42 |
然后,main()调用了foo():
地址 | 名字 | 值 |
3 | c | 1 |
2 | b | 100 |
1 | a | 5 |
0 | x | 42 |
foo()又调用了bar():
地址 | 名字 | 值 |
4 | i | 6 |
3 | c | 1 |
2 | b | 100 |
1 | a | 5 |
0 | x | 42 |
WOW!我们的栈涨的很高了.
ber()结束以后,它的栈被释放,只剩foo()和main()了:
地址 | 名字 | 值 |
3 | c | 1 |
2 | b | 100 |
1 | a | 5 |
0 | x | 42 |
然后foo()结束后,只剩下了main()函数:
地址 | 名字 | 值 |
0 | x | 42 |
这样就结束了.明白了么?这就像叠盘子:在顶部添加,从顶部拿走.