17611538698
webmaster@21cto.com

Rust 内存安全之技术剖析

编程语言 0 101 2024-04-09 10:55:24

图片

在过去的十年里,Rust已经成为想要编写快速、原生机器软件开发者们的首选语言,因为它对内存安全有着强有力的保证。

其他语言(如 C)可能运行速度快且接近机器金属层,但它们缺乏确保程序内存正确分配和处置的语言功能。

就像白宫国家网络管理办公室最近指出的那样,这些缺陷导致软件不安全和被利用,从而在现实世界中造成代价高昂的后果。像 Rust 这样将内存安全放在第一位的语言正受到越来越多的用户关注。

Rust 如何以其他语言无法做到的方式保证内存安全?我们就来了解一番。

Rust 内存安全:本机语言功能


关于 Rust 的内存安全功能,首先我们要了解的是,它们不是通过库或外部分析工具提供的,这两者都是可选的。


Rust 的内存安全功能已融入该语言中。它们不仅是强制性的,而且是在代码运行之前要强制执行的。


在 Rust 中,内存不安全的行为不会被视为运行时错误,而是被视为编译器错误。


Rust 中的所有问题(例如释放后使用错误)均提示语法上是错误的。此类无效代码不会进入编译阶段,也不会投入生产环境。在许多语言(包括 C 或 C++)中,内存安全错误通常只能在运行时发现。


但这并不意味着用 Rust 编写的代码完全无懈可击或万无一失。一些运行时问题,例如“竞争条件”,这仍然是开发者的责任。但 Rust 确实消除了许多常见的软件漏洞利用机会。

内存管理语言(例如C#、Java或Python)几乎完全让开发者无需进行任何手动内存管理。开发者可以专注于编写代码和完成工作。但这种便利需要付出一些其他代价,通常是速度或需要更长的运行时间。

而Rust 的二进制文件非常紧凑,默认以机器本机速度运行,并且一直保持内存安全。

Rust 变量:默认不可变


Rust 新手开发人员首先学到的一件事是,默认情况下所有变量都是不可变的——这意味着它们不能被重新分配或修改。必须将它们明确声明为可变才可能更改。


这可能看起来微不足道,但它的最终效果是迫使开发者充分意识到程序中哪些值需要可变以及何时可变。因此,生成的代码更容易推理,因为它告诉你可以更改什么以及在哪里更改。


默认不可变与语言中常量概念不同。不可变变量可以被计算,然后在运行时存储为不可变型,也就是说,它可以被计算、存储,然后不能被更改。但是,在程序运行之前,常量必须在编译时可计算。许多类型的值(例如用户输入)不能以这种方式存储为常量。


C++ 的假设与 Rust 相反:默认情况下,一切都是可变的。你必须使用const关键字来声明事物不可变。我们可以const采用默认使用的 C++ 编码风格 ,但这只会覆盖自己编写的代码。Rust 确保所有用该语言编写的程序,现在和将来都默认假定不变性。


Rust 中的所有权、借用和引用


Rust 中的每个值都有一个“所有者”,这意味着在代码中的任何给定点,一次只有一件事可以对值具有完全的读/写控制。


所有权可以暂时放弃或“借用”,但这种行为会被 Rust 的编译器严格跟踪。任何违反给定对象所有权规则的代码都将无法编译。


我们将这种方法与我们在其他语言中看到的方法进行对比。


在 C 语言中,没有所有权:任何东西都可以随时被任何其他东西访问。如何修改事物的所有责任都由开发者或程序员们承担。


在 Python、Java 或 C# 等托管式语言中,没有这种所有权规则,但这只是因为它们不需要。对象访问以及内存安全由运行时处理。同样,这是以速度或运行时的大小和存在为代价的。


Rust 的生命周期


Rust 中对值的引用不仅有所有者,还有生命周期——这意味着给定引用有效的范围。


在大多数 Rust 代码中,生命周期可以是隐式的,因为编译器会跟踪它们。但对于更复杂的用例,也可以显式注释生命周期。无论如何,尝试访问或修改其生命周期之外的内容或在其“超出范围”之后都会导致编译器错误。这再次防止了所有类别的危险错误通过 Rust 代码进入生产环境。


当你尝试访问理论上已被释放或超出范围的内容时,会出现释放后使用错误或“悬空指针”。


这些在 C 和 C++ 中非常常见。C 在编译时没有正式强制执行对象生命周期。C++ 有“智能指针”之类的概念来避免这种情况,但默认情况下它们并没有实现;你必须选择使用它们。语言安全成为个人编码风格或机构要求的问题,而不是语言完全确保的事情。


对于 Java、C# 或 Python 等托管语言,内存管理是语言运行时的职责。这是以需要大量运行时间为代价的,有时还会降低执行速度。Rust 在代码运行之前强制执行生命周期规则。


Rust 的内存安全成本


Rust 的内存安全也有成本,第一个也是最大的成本,是学习和使用语言本身的需要。


切换到一门新语言从来都不是一件容易的事,Rust 的常见批评之声是它的初始学习曲线,即使对于经验丰富的程序员也是如此。掌握 Rust 的内存管理模型需要一定的时间和精力。即使在该语言的支持者中,Rust 的学习曲线也是一个不断讨论的话题。


C、C++ 以及其他语言都拥有庞大且根深蒂固的用户群,这是他们经常被争论的焦点。他们还拥有大量可以利用的现有代码,包括库和完整的应用程序。不难理解为什么开发人员选择使用 C 语言:围绕它们存在如此多的工具和其他资源。


在 Rust 存在的十年左右的时间里,它拥有了工具、文档以及用户社区,可以更轻松地跟上步伐。第三方“板条箱”或 Rust 库的集合已经非常广泛并且每天都在增长。


使用 Rust 可能需要一段时间的重新培训和工具熟悉,但用户不会缺少给定任务的资源或库支持。


将 Rust 的优势应用到其它编程语言


Rust 的发展引发了关于将缺乏内存安全性的现有语言进行改造以采用类似 Rust 的内存保护功能的讨论。


业界中有一些雄心勃勃的改造想法,但它们也很难实施。其一,它们几乎肯定是以向后兼容性为代价的。Rust 的行为很难引入到不使用它们的语言中,除非在现有遗留代码和具有新行为的新代码之间强制进行严格划分。


但是这些都没有阻止人们的尝试。各种项目都尝试使用有关内存安全和所有权的规则来创建 C 或 C++ 的扩展。


Carbon 和 Cppfront 项目探索了这一思路。如下图示:


图片

Carbon 其实是一种全新的语言,具有针对现有 C++ 代码的迁移工具,并且 Cppfront 提出了 C++ 的替代语法,作为更安全、更方便地编写它的方式。


以上说的两个项目都仍处于原型阶段。Cppfront 于 2024 年 3 月已经发布了第一个功能完整的版本。


结语

Rust 在编程世界中拥有独特地位的原因在于,它最强大、最显着的特性——内存安全性和保证它的编译时行为——是该语言不可分割的一部分;它们是内置的,而不是在事后添加的。

访问这些功能最初可能需要更多的开发人员,但红利在稍后就会得到回报。

作者:洛逸

评论