跳转到内容
← 返回先驱
系统软件先驱1941–201113 分钟阅读

丹尼斯·里奇

Dennis Ritchie

有一种贡献,你每天在使用它,却从未意识到它的存在。你打开的每一个 App,运行的每一段服务器代码,嵌入在你汽车里的每一行固件——它们的绝大多数都用 C 语言写成,或者运行在 Unix 及其后代(Linux、macOS、Android、iOS 的内核)之上。 丹尼斯·里奇(Dennis Ritchie)是 C 语言的发明…

C语言Unix操作系统系统编程

有一种贡献,你每天在使用它,却从未意识到它的存在。你打开的每一个 App,运行的每一段服务器代码,嵌入在你汽车里的每一行固件——它们的绝大多数都用 C 语言写成,或者运行在 Unix 及其后代(Linux、macOS、Android、iOS 的内核)之上。

丹尼斯·里奇(Dennis Ritchie)是 C 语言的发明者,也是与肯·汤普森(Ken Thompson)一起创造 Unix 的人。他 2011 年悄然去世,享年 70 岁,世界几乎没有注意到——仅仅一周前,苹果公司创始人史蒂夫·乔布斯去世,全球媒体铺天盖地。然而,乔布斯建造的 Mac、iPhone 和 iPad,其深层基础恰恰是里奇和汤普森在贝尔实验室的工作。

破除误解:C 语言不是偶然发明的

一种常见的叙述把 C 语言描绘成"为了写 Unix 而顺手发明的工具"。这低估了其中的理论深度和设计哲学。

里奇在设计 C 语言时面对的是一个真实的工程矛盾:当时写操作系统只有两种选择——用汇编语言(快速但难以移植)或用 FORTRAN 这类高级语言(便携但太慢,且不能控制底层硬件)。C 语言的设计目标是成为第三种选择:一种足够接近硬件(指针、内存直接操作)、同时又具备结构化编程能力的语言。这个设计目标本身就是一个深刻的工程哲学判断。

现场:贝尔实验室与 Unix 的诞生

里奇 1941 年 9 月 9 日生于纽约州布朗克斯维尔,父亲阿利斯泰尔·里奇(Alistair Ritchie)是贝尔实验室的电气工程师。他在哈佛取得物理学本科学位(1963),并在 1968 年完成了关于"计算复杂性与程序结构"的博士论文(一则广为流传的趣闻称,他因未提交装订本而始终未正式领取博士学位),随后加入贝尔实验室,在那里工作了整个职业生涯。

1960 年代末,贝尔实验室退出了 MIT 主导的 Multics 操作系统项目——那个项目太过庞大复杂。汤普森和里奇决定从头开始,构建一个更简单、更优雅的操作系统。他们首先把它跑在一台被抛弃的 PDP-7 小型机上,汤普森的妻子去度假的时候,他用三周时间写出了早期 Unix 的核心:内核、shell 和一个文件系统编辑器。

Unix 最初用汇编语言写成。1969–1973 年间,里奇发明 C 语言的过程,与把 Unix 改写成 C 语言的过程几乎是同步进行的。C 语言不是先设计好再用的工具,而是在实践中被需求逼出来的——Unix 的每一个模块,都在检验 C 语言的能力边界。

C 语言的核心设计哲学

C 语言的设计哲学可以用一句话总结:信任程序员

特性设计决策原因
指针程序员直接操作内存地址操作系统需要精确控制内存
无运行时检查数组越界不报错,由程序员保证最大性能,最小开销
结构化控制流if/while/for 取代 goto程序可读性与可维护性
移植性同一代码可在不同硬件上编译Unix 需要跨越多种机器
紧凑语法表达式可以组合嵌套减少打字量(打字机键盘时代)

1978 年,里奇与布莱恩·克尼汉(Brian Kernighan)合著《C 程序设计语言》(The C Programming Language),即著名的"K&R C"。这本书以简洁精确而著称,在程序员群体中几乎具有宗教典籍的地位。书中的第一个例子——打印"hello, world"——成为了所有编程教学的传统起点。

从 B 到 C:类型为什么必不可少

B 语言是"无类型"的:所有值都是一个机器字,算术一律按字进行。这在按字寻址的 PDP-7 上没有矛盾——地址加一,天然就是下一个字。但 1970 年贝尔实验室添置的 PDP-11 是按字节寻址的 16 位机器,还计划支持浮点运算。在字节机器上,"地址加一"究竟移动一个字节还是一个字,取决于你指向的是什么东西。B 的语义模型在这里暴露出一个无法回避的缺口:指针运算需要类型信息,否则编译器不知道该如何缩放偏移量。

1971 年,里奇开始给 B 补上类型——先是 char 和 int,然后是指向它们的指针和数组。他把过渡版本称为 NB(New B);随着汤普森开始用 NB 改写 Unix 内核、不断提出实际需求,语言被继续打磨,最终定名为 C。汤普森→B、里奇→NB→C,这条谱系说明 C 的类型不是学院式的设计,而是被一台具体机器的寻址方式逼出来的。

值得注意的是 C 中类型的职责定位:它告诉编译器一段内存该如何解释、偏移量该如何缩放,而不是用来约束程序员不许做什么。这是一条描述性的类型系统,不是防御性的——生成的代码因此可以贴近手写汇编,代价是类型错误不会被语言拦住。后来的 Ada、Rust 把类型的职责从"描述表示"扩展为"限制行为",走的正是另一条岔路。

里奇本人对 C 的评价异常冷静。在 1993 年回顾 C 语言历史的论文结尾,他写道:C 是"古怪的、有缺陷的,同时是一个巨大的成功"("quirky, flawed, and an enormous success")。他知道缺陷在哪里——许多就藏在上述的设计取舍里。

两个"没有":指针算术与缺席的字符串类型

C 最招批评的两个特征,恰恰都是有意为之。

为什么有指针算术:在 C 里,数组名在表达式中会"退化"为指向首元素的指针,p + 1 不是地址加一,而是前进一个元素。这不是炫技:它让"遍历一块内存"直接映射到机器的寻址方式上,编译器几乎不需要插入额外指令。PDP-11 甚至有专门的自增寻址模式,C 的 *p++ 可以用一条指令完成。抽象的代价被压到接近零——这正是系统语言想要的东西。

为什么没有字符串类型:C 没有 string 类型,字符串只是"以 \0 结尾的字符数组"这一约定。原因在于机器没有字符串硬件:一个内建类型必须对应机器能直接操作的表示,否则就要引入隐藏的内存分配和长度管理——而"隐藏的开销"正是 C 要避免的。后果同样清晰:数组不携带长度信息,越界写入不会被发现,strcpy 这类函数把安全责任完全交给调用者。数十年后,这一约定成为缓冲区溢出漏洞的温床。里奇并非看不见风险,而是明确把风险定价为"程序员的责任"——这笔账直到今天还在被重新计算。

Unix 的哲学遗产

Unix 不只是一个操作系统,它还携带了一套完整的软件哲学,里奇和汤普森将其内化在设计决策中。其中最根本的一条常被单独称为"一切皆文件":硬盘文件、终端、打印机、进程间通道,在 Unix 中都呈现为文件,用同一组 open/read/write/close 系统调用操作。这个设计的机制收益在于接口数量的坍缩——程序不需要知道自己在和什么设备打交道,学会操作文件就学会了操作一切;把输出重定向到文件、终端或另一个程序,程序本身一行代码都不用改。对比同时代每种设备各有一套专用命令的系统,就能理解这个选择省掉了多少心智负担。它也为管道铺平了道路:既然一切都是字节流,任何程序的输出都可以是任何程序的输入。

其余几条同样朴素:

  1. 做好一件事:每个程序只做一件事,把它做好。
  2. 程序的输出是另一个程序的输入:用管道(pipe)组合小工具,而不是构建庞大的单体程序。
  3. 早期发布,频繁迭代:Unix 以惊人的速度迭代,每个版本都接受真实用户的检验。
  4. 文本是通用接口:程序之间以纯文本传递数据,使得任何程序都能与任何其他程序互操作。

这套哲学(被后人总结为"Unix 哲学")深刻影响了开源软件运动、Linux 的设计,以及今天的 DevOps 文化。

可移植性的革命

1973 年,Unix 被用 C 语言重写,这是软件史上的一个转折点。在此之前,操作系统必须针对每种硬件从头用汇编写一遍,移植成本极高。C 语言写就的 Unix 只需重写很小一部分与硬件直接交互的代码,就能在新的机器上运行。

1970 年代,Unix 的 C 语言源码被许可给各大学使用(当时 AT&T 受到反垄断法约束,不得在电信以外的领域商业化),催生了 BSD Unix 等衍生版本,孕育了整整一代程序员和研究者。1991 年,林纳斯·托瓦兹(Linus Torvalds)在 Unix 的直接影响下创造了 Linux。今天全球 TOP500 超级计算机已 100% 运行 Linux(自 2017 年 11 月起),还有数十亿部 Android 手机以及大量服务器同样运行在 Linux 内核上——而 Linux 的设计语言正是 C。

图灵奖与低调的一生

1983 年,里奇与汤普森共同获得 ACM 图灵奖,表彰他们"对通用操作系统理论的贡献,特别是 Unix 操作系统的实现"。此外,他们还共同获得 1990 年 IEEE 理查德·哈明奖章和 1998 年美国国家技术奖章(由克林顿总统颁发)。

里奇以极度低调著称,拒绝接受大量媒体采访,几乎不在公开场合发言。他在同一间贝尔实验室办公室工作了三十余年,直到退休。2011 年 10 月 12 日,他在新泽西州家中被发现独自去世,已有数日,此前他一直在与前列腺癌和心脏病作斗争。

开源社区的一句评语流传甚广:"乔布斯是个商人,里奇才是真正改变世界的人。"

跨域连接

  • 道德风险:"信任程序员"这条哲学的实质,是把安全成本从写代码的一方挪走。越界不报错、代价延后到运行时、且往往由第三方用户承担,于是决策者面对的价格是错的。后来以类型系统强制所有权的路线,做的正是把这部分外部成本内部化到编译期。推论可测:不改变个人技能,只改变默认,漏洞的构成比例就会移动。
  • 语系:编程语言的"亲缘"与自然语言的谱系不是一回事。后者靠共同祖先的规则演变,前者靠设计者的有意借用,因此同一特性会在互不相邻的分支上反复出现——水平传递多于垂直继承。方法上的后果很明确:比较语言学能用规则对应证同源,语言设计史只能靠文献与人事关系去证。
  • 外部性:他创造了被使用最广的语言与操作系统,公众认知里却几乎不留位置。这不是运气问题:通用层的价值分散在全部下游,创造者无法捕获,因此供给会低于社会最优。历史上补足这块缺口的一直是贝尔实验室、大学与基金会这类机构;一旦它们收缩,基础层的维护会先于应用层出现人手短缺。
  • 遗传密码:遗传密码表在几乎所有生物中通用,不是因为它最优,而是任何改动都会同时打乱已有的全部蛋白,依赖者越多界面越冻结。C 的调用约定与标准库接口处在同一位置:它们的稳定性来自下游数量,与设计优越无关。推论是,越底层的界面越难改,改动窗口只在生态早期开着。
  • 类型系统:C 的类型主要用来告诉编译器如何解释一段内存、如何做算术提升,而不是用来阻止错误。这条定位一次性解释了它为何既高效又危险,也解释了后来者的分岔:把类型的职责从"描述表示"改成"限制可达状态",才可能在编译期挡住整类错误。

遗忘的代价

里奇的低调是一个值得思考的历史现象。他创造了人类使用最广泛的编程语言之一,以及半个世纪内主导计算机领域的操作系统,却没有进入公众视野。与此相对,乔布斯、比尔·盖茨等商业人物享有极高的社会知名度。

这反映了技术史的一个规律:基础设施的创造者往往比应用层的构建者更难被看见。里奇创造了世界,但他把它交给了别人来使用和营销。他的碑文,如果有的话,应该是今天任何一台 Linux 服务器、Android 手机或嵌入式设备的引导画面。

参考文献

  • Kernighan, B. W. & Ritchie, D. M. The C Programming Language. 2nd ed. Prentice Hall (1988). (K&R C,程序员圣经)
  • Salus, P. H. A Quarter Century of Unix. Addison-Wesley (1994). (Unix 历史的标准参考)
  • Ritchie, D. M. & Thompson, K. The UNIX Time-Sharing System. Communications of the ACM 17(7) (1974): 365–375. (Unix 设计的第一篇公开论文)
  • Ritchie, D. M. The Development of the C Language. ACM SIGPLAN History of Programming Languages Conf. (1993). (里奇本人对 C 语言历史的回顾)

延伸阅读

  • Kernighan, B. W. Unix: A History and a Memoir. Self-published (2019). (来自最亲密合作者的历史叙述)