Heap & Stack

    操作系统会将物理内存映射成虚拟地址空间,程序在启动时看到的虚拟地址空间是一块完整连续的内存。

    栈内存从高位地址向下增长,且栈内存分配是连续的,一般操作系统对栈内存大小是有限制的,Linux/Unix 类系统上面可以通过 ulimit 设置最大栈空间大小,所以 C 语言中无法创建任意长度的数组。在Rust里,函数调用时会创建一个临时栈空间,调用结束后 Rust 会让这个栈空间里的对象自动进入 流程,最后栈顶指针自动移动到上一个调用栈顶,无需程序员手动干预,因而栈内存申请和释放是非常高效的。

    相对地,堆上内存则是从低位地址向上增长,堆内存通常只受物理内存限制,而且通常是不连续的,一般由程序员手动申请和释放的,如果想申请一块连续内存,则操作系统需要在堆中查找一块未使用的满足大小的连续内存空间,故其效率比栈要低很多,尤其是堆上如果有大量不连续内存时。另外内存使用完也必须由程序员手动释放,不然就会出现内存泄漏,内存泄漏对需要长时间运行的程序(例如守护进程)影响非常大。

    由于函数栈在函数执行完后会销毁,所以栈上存储的变量不能在函数之间传递,这也意味着函数没法返回栈上变量的引用,而这通常是 C/C++ 新手常犯的错误。而 Rust 中编译器则会检查出这种错误,错误提示一般为 xxx does not live long enough,看下面一个例子

    其实由于 a 本身是 String 类型,是使用堆来存储的,所以可以直接返回,在函数返回时函数栈销毁后依然存在。同时 Rust 中下面的代码实际上也只是浅拷贝。

    Rust 默认使用栈来存储变量,而栈上内存分配是连续的,所以必须在编译之前了解变量占用的内存空间大小,编译器才能合理安排内存布局。

    C 里面是通过 malloc/free 手动管理堆上内存空间的,而 Rust 则有多种方式,其中最常用的一种就是 Box,通过 可以在堆上申请一块内存空间,不像 C 里面一样堆上空间需要手动调用 free 释放,Rust 中是在编译期编译器借助 lifetime 对堆内存生命期进行分析,在生命期结束时自动插入 free。当前 Rust 底层即 Box 背后是调用 jemalloc 来做内存管理的,所以堆上空间是不需要程序员手动去管理释放的。很多时候你被编译器虐得死去活来时,那些 borrow, move, 错误其实就是编译器在教你认识内存布局,教你用 lifetime 规则去控制内存。这套规则说难不难,说简单也不简单,以前用别的语言写程序时对内存不关心的,刚写起来可能真的会被虐得死去活来,但是一旦熟悉这套规则,对内存布局掌握清楚后,借助编译器的提示写起程序来就会如鱼得水,这套规则是理论界研究的成果在Rust编译器上的实践。

    大多数带 GC 的面向对象语言里面的对象都是借助 box 来实现的,比如常见的动态语言 Python/Ruby/JavaScript 等,其宣称的”一切皆对象(Everything is an object)”,里面所谓的对象基本上都是 boxed value。

    Box 是堆上分配的内存,通过 Box::new() 会创建一个堆空间并返回一个指向堆空间的指针

    nightly 版本中引入 box 关键词,可以用来取代 Box::new() 申请一个堆空间,也可以用在模式匹配上面

    下面看一个例子,对比一下 Vec<i32> 和 内存布局,这两个图来自 ,从这两张内存分布图可以清楚直观地看出 Box 是如何存储的

    一些语言里会有看起来既像数组又像列表的数据结构,例如 python 中的 List,其实就是与 Vec<Box<i32>> 类似,只是把 i32 换成任意类型,就操作效率而言比单纯的 List 高效,同时又比数组使用更灵活。

    另外,需要从函数中返回一个浅拷贝的变量时也需要使用堆内存而不能直接返回一个指向函数内部定义变量的引用。