Appearance
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 是防止更严重内存安全错误的重要机制。