西安建设网站公司,许昌网站建设费用,平面排版网站,专门设计网站的公司叫什么文章目录 栈、堆、所有权栈(Stack)与堆(Heap)栈堆性能区别所有权与堆栈 所有权原则变量作用域所有权与函数返回值与作用域 栈、堆、所有权
栈(Stack)与堆(Heap)
栈和堆是编程语言最核心的数据结构#xff0c;但是在很多语言中#xff0c;你并不需要深入了解栈与堆。 但对于… 文章目录 栈、堆、所有权栈(Stack)与堆(Heap)栈堆性能区别所有权与堆栈 所有权原则变量作用域所有权与函数返回值与作用域 栈、堆、所有权
栈(Stack)与堆(Heap)
栈和堆是编程语言最核心的数据结构但是在很多语言中你并不需要深入了解栈与堆。 但对于 Rust 这样的系统编程语言值是位于栈上还是堆上非常重要, 因为这会影响程序的行为和性能。
栈和堆的核心目标就是为程序在运行时提供可供使用的内存空间。
栈
栈按照顺序存储值并以相反顺序取出值这也被称作后进先出。想象一下一叠盘子当增加更多盘子时把它们放在盘子堆的顶部当需要盘子时再从顶部拿走。不能从中间也不能从底部增加或拿走盘子
增加数据叫做进栈移出数据则叫做出栈。
因为上述的实现方式栈中的所有数据都必须占用已知且固定大小的内存空间假设数据大小是未知的那么在取出数据时你将无法取到你想要的数据。
堆
与栈不同对于大小未知或者可能变化的数据我们需要将它存储在堆上。
当向堆上放入数据时需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位把它标记为已使用并返回一个表示该位置地址的指针, 该过程被称为在堆上分配内存有时简称为 “分配”(allocating)。
接着该指针会被推入栈中因为指针的大小是已知且固定的在后续使用过程中你将通过栈中的指针来获取数据在堆上的实际内存位置进而访问该数据。
由上可知堆是一种缺乏组织的数据结构。想象一下去餐馆就座吃饭: 进入餐馆告知服务员有几个人然后服务员找到一个够大的空桌子堆上分配的内存空间并领你们过去。如果有人来迟了他们也可以通过桌号栈上的指针来找到你们坐在哪。
性能区别
写入方面入栈比在堆上分配内存要快因为入栈时操作系统无需分配新的空间只需要将新数据放入栈顶即可。相比之下在堆上分配内存则需要更多的工作这是因为操作系统必须首先找到一块足够存放数据的内存空间接着做一些记录为下一次分配做准备。
读取方面得益于 CPU 高速缓存使得处理器可以减少对内存的访问高速缓存和内存的访问速度差异在 10 倍以上栈数据往往可以直接存储在 CPU 高速缓存中而堆数据只能存储在内存中。访问堆上的数据比访问栈上的数据慢因为必须先访问栈再通过栈上的指针来访问内存。
因此处理器处理分配在栈上数据会比在堆上的数据更加高效。
所有权与堆栈
当你的代码调用一个函数时传递给函数的参数包括可能指向堆上数据的指针和函数的局部变量依次被压入栈中当函数调用结束时这些值将被从栈中按照相反的顺序依次移除。
因为堆上的数据缺乏组织因此跟踪这些数据何时分配和释放是非常重要的否则堆上的数据将产生内存泄漏 —— 这些数据将永远无法被回收。这就是 Rust 所有权系统为我们提供的强大保障。
对于其他很多编程语言你确实无需理解堆栈的原理但是在 Rust 中明白堆栈的原理对于我们理解所有权的工作原理会有很大的帮助。 所有权原则
理解了堆栈接下来看一下关于所有权的规则首先请谨记以下规则
1. Rust 中每一个值都被一个变量所拥有该变量被称为值的所有者
2. 一个值同时只能被一个变量所拥有或者说一个值只能拥有一个所有者
3. 当所有者(变量)离开作用域范围时这个值将被丢弃(drop)
变量作用域
作用域是一个变量在程序中有效的范围, 假如有这样一个变量
let s hello;变量 s 绑定到了一个字符串字面值该字符串字面值是硬编码到程序代码中的。s 变量从声明的点开始直到当前作用域的结束都是有效的
{ // s 在这里无效它尚未声明let s hello; // 从此处起s 是有效的// 使用 s
} // 此作用域已结束s不再有效简而言之s 从创建伊始就开始有效然后有效期持续到它离开作用域为止可以看出就作用域来说Rust 语言跟其他编程语言没有区别。
所有权与函数
将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制就像赋值语句一样。以下示例使用注释展示变量何时进入和离开作用域 代码
fn main() {let s String::from(hello); // s 进入作用域takes_ownership(s); // s 的值移动到函数里 ...// ... 所以到这里不再有效let x 5; // x 进入作用域makes_copy(x); // x 应该移动函数里// 但 i32 是 Copy 的所以在后面可继续使用 x} // 这里, x 先移出了作用域然后是 s。但因为 s 的值已被移走// 所以不会有特殊操作fn takes_ownership(some_string: String) { // some_string 进入作用域println!({}, some_string);
} // 这里some_string 移出作用域并调用 drop 方法。占用的内存被释放fn makes_copy(some_integer: i32) { // some_integer 进入作用域println!({}, some_integer);
} // 这里some_integer 移出作用域。不会有特殊操作
返回值与作用域
返回值也可以转移所有权。以下示例 一样带有类似的注释。 运行结果 返回unused警告
代码
fn main() {let s1 gives_ownership(); // gives_ownership 将返回值// 移给 s1let s2 String::from(hello); // s2 进入作用域let s3 takes_and_gives_back(s2); // s2 被移动到// takes_and_gives_back 中,// 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域但已被移走// 所以什么也不会发生。s1 移出作用域并被丢弃fn gives_ownership() - String { // gives_ownership 将返回值移动给// 调用它的函数let some_string String::from(yours); // some_string 进入作用域some_string // 返回 some_string 并移出给调用的函数
}// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) - String { // a_string 进入作用域a_string // 返回 a_string 并移出给调用的函数
}变量的所有权总是遵循相同的模式将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时其值将通过 drop 被清理掉除非数据被移动为另一个变量所有。