Rust学习教程10 - 所有权

Rust通过所有权系统在编译时确保内存安全,避免了垃圾回收的性能损失。所有权规则规定每个值都有一个所有者,所有者离开作用域时,值会被丢弃。本文介绍了栈和堆的区别,强调了所有权在堆栈管理中的作用,以及所有权在函数传值与返回中的应用。通过示例展示了所有权转移、克隆和拷贝的概念,帮助理解Rust中变量绑定背后的数据交互。
摘要由CSDN通过智能技术生成

本文节选自<<Rust语言圣经>>一书
欢迎大家加入Rust编程学院,一起学习交流:
QQ群:1009730433

所有权

所有的程序都必须和计算机内存打交道,如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,成了重中之重,也是所有编程语言设计的难点之一。在计算机语言不断演变过程中,出现了三种流派:

  • 垃圾回收机制(GC),在程序运行时不断寻找不再使用的内存,典型代表:Java、Go
  • 手动管理内存的分配和释放, 在程序中,通过函数调用的方式来申请和释放内存,典型代表:C++
  • 通过所有权来管理内存,编译器在编译时会根据一系列规则进行检查

其中Rust选择了第三种,最妙的是,这种只发生在编译器,因此对于程序运行期,不会有任何性能上的损失。

因为所有权是一个新概念,因此读者需要花费一些时间来掌握它,一旦掌握,海阔天空任你跃,在本章,我们将通过字符串来引导讲解所有权的相关知识。

一段不安全的代码

先来看看C语言的一段糟糕代码:

int* foo() {
   
    int a;          // 变量a的作用域开始
    a = 100;
    char *c = "xyz";   // 变量c的作用域开始
    return &a;
}                   // 变量a和c的作用域结束

这段代码虽然可以编译通过,但是其实非常糟糕,变量ac都是局部变量,函数结束后将局部变量a的地址返回,但局部变量a存在栈中,在离开作用域后,局部变量所申请的栈上内存都会被系统回收,从而造成了悬空指针(Dangling Pointer)的问题。这是一个非常典型的内存安全问题。很多编程语言都存在类似这样的内存安全问题。再来看变量cc的值是常量字符串,存储于常量区,可能这个函数我们只调用了一次,我们可能不再想使用这个字符串,但xyz只有当整个程序结束后系统才能回收这片内存,这点让程序员是不是也很无奈?

所以内存安全问题,一直都是程序员非常头疼的问题,好在在Rust中,这些问题即将成为历史,那Rust如何做到这一点呢?

在正式进入主题前,先来一个预热知识。

栈(Stack)与堆(Heap)

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

栈和堆的核心目标就是为程序在运行时提供可供使用的内存空间,关于它们的详细解释和实现方式,请参见Rust代码鉴赏一书.

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

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

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

当向堆上放入数据时,你需要请求一定大小的空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针。这个过程称作 在堆上分配内存,有时简称为 “分配”(allocating)。 接着,该指针会被推入中,因为指针的大小时已知且固定的,在后续使用过程中,你将通过栈中的指针,来获取数据在堆上的实际内存位置,进而访问该数据。

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

性能区别

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

访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。得益于CPU高速缓存,现代处理器访问内存的次数越少则越快。继续类比,假设有一个服务员在餐厅里处理多个桌子的点菜,在一个桌子报完所有菜后再移动到下一个桌子是最有效率的。但是如果在桌子A点菜完,再去一个比较远的桌子B点菜,就会比较慢。

出于同样原因,处理器在处理栈上数据的时候比处理堆上的数据更加高效,同时,在堆上分配大量的空间也可能消耗时间。

所有权与堆栈

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

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

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

所有权原则

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

  1. Rust中每一个值都有且只有一个所有者(变量)
  2. 当所有者(变量)离开作用域范围时,这个值将被丢弃
变量作用域

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

let s = "hello"

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值