程序中为什么要区分堆和栈?
栈内存从高位地址向下增长,且栈内存分配是连续的,一般操作系统对栈内存大小是有限制的,编译器自动分配和回收 无需程序员手动干预,因而栈内存申请和释放是非常高效的。
由于函数栈在函数执行完后会销毁,所以栈上存储的变量不能在函数之间传递,这也意味着函数没法返回栈上变量的 引用,变量的生存周期有限制。
Rust 默认使用栈来存储变量,而栈上内存分配是 连续的,所以必须在编译之前了解变量占用的内存空间大小,编译器才能合理安排内存布局。
堆 不是自动分配和回收
堆上内存则是从低位地址向上增长,堆内存通常只受物理内存限制,而且通常是不连续的,一般由程序员手动申请和释放的,如果想申请一块连续内存,则操作系统需要在堆中查找一块未使用的满足大小的连续内存空间,故其效率比栈要低很多,尤其是堆上如果有大量不连续内存时。另外内存使用完也必须由程序员手动释放,不然就会出现内存泄漏,内存泄漏对需要长时间运行的程序(例如守护进程)影响非常大。
由于 a 本身是 String 类型,是使用堆来存储的,所以可以直接返回,在函数返回时函数栈销毁后依然存在。同时 Rust 中下面的代码实际上也只是浅拷贝。
零钱和大额的使用方式
大多数带 GC 的面向对象语言里面的对象都是借助 box 来实现的,比如常见的动态语言 Python/Ruby/JavaScript 等,其宣称的”一切皆对象(Everything is an object)”,里面所谓的对象基本上都是 boxed value。
boxed 值相对于 unboxed,内存占用空间会大些,同时访问值的时候也需要先进行 unbox,即对指针进行解引用再获取真正存储的值,所以内存访问开销也会大些。既然 boxed 值既费空间又费时间,
为什么还要这么做呢?
因为通过box,所有对象看起来就像是以相同大小存储的,因为只需要存储一个指针就够了,应用程序可以同等看待各种值,而不用去管实际存储是多大的值,如何申请和释放相应资源。
一般而言,在编译期间不能确定大小的数据类型都需要使用堆上内存,因为编译器无法在栈上分配 编译期未知大小 的内存,所以诸如 String, Vec 这些类型的内存其实是被分配在堆上的。换句话说,我们可以很轻松的将一个 Vec move 出作用域而不必担心消耗,因为数据实际上不会被复制。
另外,需要从函数中返回一个浅拷贝的变量时也需要使用堆内存而不能直接返回一个指向函数内部定义变量的引用。