Skip to content

10.3 内存安全

在谈到 Rust 的时候,经常会提到的一个概念,那就是“内存安全”(Memory safety)。 内存安全是 Rust 设计的主要目标之一,因此我们有必要把这个概念做一个澄清,让大家能更清楚地理解 Rust 为什么要这么设计。

在 Wikipedia 上,内存安全是这么定义的:

Memory safety is the state of being protected from various software bugs and security vulnerabilities when dealing with memory access,such as buffer overflows and dangling pointers.

下面列举一系列的“内存不安全”的例子。以下这些情况,就是 Rust 想要避免的问题。

  • 空指针 解引用空指针是不安全的。这块地址空间一般是受保护的,对空指针解引用在大部分平台上会产生 segfault。

  • 野指针 野指针指的是未初始化的指针。它的值取决于它这个位置以前遗留下来的是什么值。所以它可能指向任意一个地方。 对它解引用,可能会造成 segfault,也可能不会,纯粹凭运气。但无论如何,这个行为都不会是你预期内的行为,是一定会产生 bug 的。

  • 悬空指针 悬空指针指的是内存空间在被释放了之后,继续使用。它跟野指针类似,同样会读写已经不属于这个指针的内容。

  • 使用未初始化内存 不只是指针类型,任何一种类型不初始化就直接使用都是危险的,造成的后果我们完全无法预测。

  • 非法释放 内存分配和释放要配对。如果对同一个指针释放两次,会制造出内存错误。如果指针并不是内存分配器返回的值,对其执行释放操作,也是危险的。

  • 缓冲区溢出 指针访问越界了,结果也是类似于野指针,会读取或者修改临近内存空间的值,造成危险。

  • 执行非法函数指针 如果一个函数指针不是准确地指向一个函数地址,那么调用这个函数指针会导致一段随机数据被当成指令来执行,是非常危险的。

  • 数据竞争 在有并发的场景下,针对同一块内存同时读写,且没有同步措施。

以上这些问题都是极度危险的,而且它们并不一定会在发生的时候就被发现并立即终止。 它们不一定会直接触发 core dump,有可能程序一直带病运行,只是结果一直有 bug 但却无法找到原因,因为真正的原因与表现之间没有任何肉眼可见的关联关系。 它们有可能造成非常随机的、难以复现和难以调试的诡异 bug,就像武林高手一样神出鬼没,行踪不定。 它们也可能在经过许多步骤之后最终触发 core dump,可惜此时早已不是案发第一现场,修复这种 bug 的难度极高。

在 Rust 语境中,还有一些内存错误是不算在“内存安全”范畴内的,比如内存泄漏以及内存耗尽。 内存泄漏显然是一种 bug,但是它不会直接造成非常严重的后果,至少比上面列出的那些错误危险性要低一些,解决的办法也是完全不一样的。 同样,内存耗尽也不是事关安全性的问题,出现内存耗尽的时候,Rust 程序的行为依然是确定性的和可控的 (目前版本下,如果内存耗尽则发生 panic,也有人认为在这种情况发生的时候,应该给个机会由用户自己处理,这种情况后面应该会有改进)。

另外,panic 也不属于内存安全相关的问题。在后面我们会花很多篇幅来讲解 panic。 panic 和 core dump 之间有重要区别。 panic 是发生不可恢复错误后,程序主动执行的一种错误处理机制;而 core dump 则是程序失控之后,触发了操作系统的保护机制而被动退出的。 发生 panic 的时候,此处就是确定性的第一现场,我们可以根据 callstack 信息很快找到事发地点,然后修复。panic 是防止更严重内存安全错误的重要机制。

Released under the MIT License