Discuz! Board

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 423|回复: 4

《rust 程序设计》【第01章】系统程序员也能享受美好

[复制链接]

19

主题

11

回帖

255

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
255
发表于 2024-6-23 12:03:48 | 显示全部楼层 |阅读模式
在某些情况(例如 Rust 的目标环境)下,比竞争对手快 10倍,哪怕只快两倍就能成为决胜的关键。速度决定了一个系统在市场上的命运,就像在硬件市场上一样。——Graydon Hoare

现在所有的计算机都支持并行……并行编程就是编程。——《结构化并行程序设计:高效计算模式》,Michael McCool等

就连 TrueType 解析器的缺陷都会被攻击者用于监视。安全性对所有软件都很重要。——Andy Wingo

读者注:关于 TrueType 缺陷分析可以查看:https://mp.weixin.qq.com/s/r059vFVulAJ747Ax-EmZ4g

我们用这 3 条引言作为本书的开篇是别有深意的。但还是先从一个“谜题”开始吧。下面的 C 程序是做什么的?代码如下:
  1. int main(int argc, char **argv) {
  2.     unsigned long a[1];
  3.     a[3] = 0x7ffff7b36cebUL;
  4.     return 0;
  5. }
复制代码
今天早上,这个程序在 Jim 的笔记本计算机上打印出了下列内容:
  1. undef: Error: .netrc file is readable by others.
  2. undef: Remove password or make file unreadable by others.
复制代码
读者注:在我的 mac 电脑上出现的是下面的情况:
  1. zsh: bus error
复制代码
然后它就崩溃了。如果你在你自己的机器上尝试运行这个程序,则可能会出现不一样的结果。这是怎么回事呢?

这个程序存在缺陷。数组 a 只有一个元素长,所以根据 C 编程语言标准,使用 a[3] 是未定义行为(undefined behavior)。

当使用一个不可移植的或错误的程序结构或者数据时,如果本国际标准对其行为没有强制要求,那么此行为就是未定义的。

未定义行为的后果不仅仅是产生难以预料的结果,事实上,它甚至会允许程序做任何事。在我们的例子中,将这个奇怪的数值存储在数组 a 的第四个元素中恰好会破坏 main 函数的调用栈,这样,当从 main 函数返回时,它不会正常退出程序,而是会跳转到 C 标准库中用以从用户主目录的文件中获取密码的代码。这可不妙。

C 和 C++ 给出了数百条规则来规避未定义行为,其中大多数是常识,比如不要访问不应该访问的内存、不要让算术运算溢出、不要除以 0,等等。但是编译器并不会强制执行这些规则——它甚至没有义务去“揭露”这些明显的违规行为。事实上,前面的程序在编译时就没有抛出错误或发出警告。作为程序员,你需要打起十二分的精神来避免这种未定义行为。

从既往经验来看,作为程序员,我们在这方面可没有什么好记录。在犹他大学学习期间,研究员 Peng Li 修改了 C 编译器和 C++ 编译器,让它们“翻译”过的程序能报告自己是否执行了某种形式的未定义行为。他发现几乎所有程序都执行过某种未定义行为,包括那些直以坚持高标准而声名卓著的项目。幻想自己就能避开 C 和 C++中的未定义行为,就像幻想只要知道了象棋的规则就能赢得比赛一样。

偶尔出现的奇怪消息或崩溃可能仅仅是质量问题,但自从 1988 年莫里斯(Morris)蠕虫利用类似上述内存越界的技术变体破译用户口令,继而在早期互联网上大规模扩散以来,未定义行为一直是造成各种安全漏洞的主要原因。

读者注:1988 年莫里斯蠕虫病毒事件:https://mp.weixin.qq.com/s/M--fJtKZOvMMF9MaAIf33w

因此 C 和 C++ 将程序员置于这样一种尴尬的境地:这些语言是系统编程的行业标准,但它们对程序员的要求实在太高了,这就注定系统会源源不断地出现崩溃和安全问题。要解开这个“谜题”,只会引出一个更大的问题:难道我们不能做得更好吗?



回复

使用道具 举报

19

主题

11

回帖

255

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
255
 楼主| 发表于 2024-6-23 13:43:25 | 显示全部楼层
1.1 Rust  为你负重前行

本章开头的 3 条引言给出了我们的答案。第三条引言来自关于震网(Stuxnet)病毒的报道,这是一种计算机蠕虫病毒,2010 年人们发现它入侵了工业控制设备。它使用许多技术来控制受害者的计算机,其中就包括负责解析文字处理软件中内嵌 TrueType 字体的相关代码的未定义行为。可以肯定的是,这段代码的作者并没有料到它以这种方式被利用,这表明需要担心安全问题的不仅仅是操作系统和服务器,任何可能处理“来源不可信数据”的软件都会成为漏洞利用的目标。

Rust 语言给了我们一个简单的承诺:只要程序通过了编译器的检查,就不会存在未定义行为。悬空指针、双重释放和空指针解引用都能在编译期捕获。数组引用则会受到编译期检查和运行期检查的双重保护,因此不会存在缓冲区溢出:想想那个不幸的 C 语言程序,它的 Rust 版代码会安全地退出并给出一条错误消息。

另外,Rust 的目标是既安全又易用。为了更好地保障程序的行为,Rust 对代码施加了比 C 和 C++ 更多的限制,而只有靠实践和经验我们才能逐渐适应这种限制。但就整体而言,这门语言还是灵活且富有表现力的。用 Rust 编写的代码范围之宽、应用领域之广就是证明。

根据我们的经验,如果该语言能发现更多错误,那么我们就可以尝试一些更具雄心的项目。如果内存管理和指针有效性问题已得到妥善处理,那么修改大型复杂程序时的风险就会降低。如果代码里的 bug 不会破坏程序中的其他部分,那么调试也会简单得多。

当然,仍然有很多 Rust 无法检测到的 bug。但实际上,只要从可能出现的问题清单中排除了未定义行为,就会让编程更美好。
回复

使用道具 举报

19

主题

11

回帖

255

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
255
 楼主| 发表于 2024-6-24 21:31:56 | 显示全部楼层
1.2 高效并行编程

众所周知,在 C 和 C++ 中恰当使用并发的难度极大。通常只有在确信单线程代码无法达到预期的性能时,开发人员才会转向并发。但正如本章第二条引言所指出的,并行性对现代机器来说太重要了,不应该等到迫不得已时才考虑它。

读者注:redis,nginx 等采用的都是单线程模式,猜测也可能是由于在 C 和 C++ 中使用并发的难度极大的原因。

事实证明,Rust 用来确保内存安全的那些限制同样能确保 Rust 程序避免产生数据竞争(data race)。只要数据不可变,你就可以在线程之间自由地共享这些数据。会发生变化的数据则只能使用同步原语访问。所有传统的并发工具仍然可用:互斥锁、条件变量、通道、原子等。Rust 只负责检查你是否正确地使用了它们。

这就让 Rust 成了一门能充分发挥现代多核机器能力的优秀语言。Rust 的生态系统提供了一些超乎于常规并发原语的库,可帮助你在处理器池之间均匀分布复杂负载、使用无锁同步机制(如读取-复制-更新)等。
回复

使用道具 举报

19

主题

11

回帖

255

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
255
 楼主| 发表于 2024-6-24 21:43:38 | 显示全部楼层
1.3 性能毫不妥协

最后,正如本章第一条引言所说的那样,Rust 同样怀揣着 Bjarne Stroustrup 在他的论文“Abstraction and the C++ Machine Model”(抽象和 C++ 机器模型)中为 C++ 阐明的雄心。

通常,C++ 的各种实现会遵循零开销原则:没用到的,就没有开销;要用到的,你也无法手写出更好的代码。

系统编程通常会涉及极限压榨机器性能。对电子游戏来说,整台机器应该极力为玩家创造最佳体验。对浏览器来说,浏览器的效率决定了内容作者可用能力的上限。在机器的固有限制下,必须将尽可能多的内存和处理器能力留给内容本身。同样的原则也适用于操作系统:内核应该尽可能把机器资源留给用户程序,而不是自己来消耗它们。

但是当我们说 Rust“快”的时候,到底意味着什么呢?毕竟人们可以用任何通用语言来写出慢速代码。更准确一点儿说,如果你准备让程序充分发挥底层机器的能力,那么 Rust 就会为你提供支持。这门语言设计了一些“高性能”的默认选项,并赋予你自主控制内存使用和处理器算力分配方式的能力。
回复

使用道具 举报

19

主题

11

回帖

255

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
255
 楼主| 发表于 2024-6-24 21:50:55 | 显示全部楼层
1.4 协作无边界

本章的标题中还隐藏了第四条引言:“系统程序员也能享受美好”。这是指 Rust 对代码共享和复用的支持。

作为 Rust 的包管理器和构建工具,Cargo 能让你轻松使用别人在 Rust 的公共包存储库 crates.io 网站上发布的各种库。只需将库的名称和所需的版本号添加到文件中,Cargo 就会负责下载这个库以及它所用到的任何其他库,并将所有内容链接在一起。你可以将 Cargo 视为 Rust 下的 NPM 或 RubyGems,其侧重于实现健全的版本管理和可重现的构建。一些广为流行的 Rust 库提供了包括开箱即用的序列化功能、HTTP 客户端和服务器功能以及现代图形 API 功能在内的一切。

再进一步说,Rust 这门语言本身也旨在支持协作:借助 Rust 的特型(trait)和泛型,我们可以创建具有灵活接口的库,将其用在许多不同的上下文中。Rust 的标准库提供了一组最核心的基本类型,这些类型为一些常见的情况建立了共享规约,以方便不同的库彼此协作。

译者注:特型,表示一组具有指定特点的类型。这里之所以把 trait 译为“特型”,是因为特型和泛型是紧密相关的,我们希望读者顾名思义就能在两者之间建立联系;而其他中文译法,或者缺乏特异性,或者难以望文生义,因此并未采纳。关于 trait 翻译与否及译法是个非常有争议的话题,我们在审读群曾经进行过至少 3 次深度讨论,但并未形成共识。对于一个常用专有名词,有一个合适的中文表述是非常必要的。但对于一个词的新译法,一开始,大家可能还不太习惯,不妨试着给新词些时间。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|DiscuzX

GMT+8, 2024-10-7 01:28 , Processed in 0.034040 second(s), 18 queries .

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表