• rust学习——栈、堆、所有权


    栈、堆、所有权

    栈(Stack)与堆(Heap)

    栈和堆是编程语言最核心的数据结构,但是在很多语言中,你并不需要深入了解栈与堆。 但对于 Rust 这样的系统编程语言,值是位于栈上还是堆上非常重要, 因为这会影响程序的行为和性能。

    栈和堆的核心目标就是为程序在运行时提供可供使用的内存空间。

    栈按照顺序存储值并以相反顺序取出值,这也被称作后进先出。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,再从顶部拿走。不能从中间也不能从底部增加或拿走盘子!

    增加数据叫做进栈,移出数据则叫做出栈

    因为上述的实现方式,栈中的所有数据都必须占用已知且固定大小的内存空间,假设数据大小是未知的,那么在取出数据时,你将无法取到你想要的数据。

    与栈不同,对于大小未知或者可能变化的数据,我们需要将它存储在堆上。

    当向堆上放入数据时,需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针, 该过程被称为在堆上分配内存,有时简称为 “分配”(allocating)。

    接着,该指针会被推入中,因为指针的大小是已知且固定的,在后续使用过程中,你将通过栈中的指针,来获取数据在堆上的实际内存位置,进而访问该数据。

    由上可知,堆是一种缺乏组织的数据结构。想象一下去餐馆就座吃饭: 进入餐馆,告知服务员有几个人,然后服务员找到一个够大的空桌子(堆上分配的内存空间)并领你们过去。如果有人来迟了,他们也可以通过桌号(栈上的指针)来找到你们坐在哪。

    性能区别

    写入方面:入栈比在堆上分配内存要快,因为入栈时操作系统无需分配新的空间,只需要将新数据放入栈顶即可。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,接着做一些记录为下一次分配做准备。

    读取方面:得益于 CPU 高速缓存,使得处理器可以减少对内存的访问,高速缓存和内存的访问速度差异在 10 倍以上!栈数据往往可以直接存储在 CPU 高速缓存中,而堆数据只能存储在内存中。访问堆上的数据比访问栈上的数据慢,因为必须先访问栈再通过栈上的指针来访问内存。

    因此,处理器处理分配在栈上数据会比在堆上的数据更加高效。

    所有权与堆栈

    当你的代码调用一个函数时,传递给函数的参数(包括可能指向堆上数据的指针和函数的局部变量)依次被压入栈中,当函数调用结束时,这些值将被从栈中按照相反的顺序依次移除。

    因为堆上的数据缺乏组织,因此跟踪这些数据何时分配和释放是非常重要的,否则堆上的数据将产生内存泄漏 —— 这些数据将永远无法被回收。这就是 Rust 所有权系统为我们提供的强大保障。

    对于其他很多编程语言,你确实无需理解堆栈的原理,但是在 Rust 中,明白堆栈的原理,对于我们理解所有权的工作原理会有很大的帮助


    所有权原则

    理解了堆栈,接下来看一下关于所有权的规则,首先请谨记以下规则:

    1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者

    2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者

    3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

    变量作用域

    作用域是一个变量在程序中有效的范围, 假如有这样一个变量:

    let s = "hello";
    
    • 1

    变量 s 绑定到了一个字符串字面值,该字符串字面值是硬编码到程序代码中的。s 变量从声明的点开始直到当前作用域的结束都是有效的:

    {                      // s 在这里无效,它尚未声明
        let s = "hello";   // 从此处起,s 是有效的
    
        // 使用 s
    }                      // 此作用域已结束,s不再有效
    
    • 1
    • 2
    • 3
    • 4
    • 5

    简而言之,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 移出作用域。不会有特殊操作
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    返回值与作用域

    返回值也可以转移所有权。以下示例 一样带有类似的注释。
    在这里插入图片描述
    运行结果
    返回unused警告
    在这里插入图片描述

    代码

    fn main() {
      let s1 = gives_ownership();         // gives_ownership 将返回值
                                          // 移给 s1
    
      let 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 并移出给调用的函数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。

  • 相关阅读:
    C语言练习百题之#include应用
    论文笔记:Leveraging Language Foundation Models for Human Mobility Forecasting
    [架构设计-1]:系统架构部门的主要角色
    Spring Data JPA
    (mac)Prometheus监控之Node_exporter(CPU、内存、磁盘、网络等)
    按键中断实验
    路面坑洼检测中的视觉算法
    上线三天破百万点赞,涵盖90%以上的Java面试题,这份Java面试神技带你所向披靡
    《向量数据库指南》——用 Milvus Cloud和 NVIDIA Merlin 搭建高效推荐系统结果
    探索人工智能的世界:构建智能问答系统之前置篇
  • 原文地址:https://blog.csdn.net/e891377/article/details/133907548