从 Python 到 Go盘点那些最早用 C 语言实现的编程语言你每天用 Python、Ruby、PHP、Lua 写代码但你可能不知道——它们最早的第一行实现几乎都是用 C 语言写的。C 语言不只是一门编程语言它更像整个软件世界的地基。过去五十多年里无数新语言选择用 C 来写下自己的第一个版本。这篇文章就按时间顺序把这些语言一个个讲清楚它们哪年诞生、由谁创造、最早为什么要用 C 实现以及它们和 C 到底是什么关系。先看一张总表年份语言创造者最早的实现方式与 C 的关系1972CDennis Ritchie贝尔实验室第一个编译器用 PDP-11 汇编写1973 年自举一切的起点1974sedLee McMahon贝尔实验室CC 实现的文本处理小语言1977awkAho、Weinberger、Kernighan贝尔实验室CC 实现的报告生成语言1978cshBill Joy加州大学伯克利C用 C 写、语法也模仿 C 的 shell1984Objective-CBrad Cox、Tom Love翻译成 C 的预编译器C 的严格超集给 C 加上面向对象1985CBjarne Stroustrup贝尔实验室Cfront 把 C 翻译成 CCfront 自身用 C 自举C 的直接继承者和超集1987PerlLarry WallCC 实现的胶水语言1988TclJohn Ousterhout伯克利CC 实现的可嵌入命令语言1989BashBrian FoxGNU 项目CC 实现的 Unix shell1991PythonGuido van RossumCCPythonC 实现的解释器语法受 C 影响1993RRoss Ihaka、Robert Gentleman奥克兰大学C 和 FortranC 实现的统计计算语言1993LuaIerusalimschy、de Figueiredo、Celes巴西 PUC-Rio纯 ANSI CC 实现的轻量嵌入式脚本语言1995Ruby松本行弘MatzCMRI/CRubyC 实现的解释器语法受 C/Perl 影响1995PHPRasmus LerdorfC从 C 写的 CGI 工具长成的语言1995JavaScriptBrendan Eich网景C第一个引擎第一个 JS 引擎用 C 写成语法借鉴 C2009GoGriesemer、Pike、ThompsonGoogleC初代 gc 编译器初代编译器用 C2015 年用 Go 自举下面我们一个个细说。一、故事要从 C 自己说起1972要讲哪些语言用 C 实现得先讲 C 自己是怎么来的——因为这里有个经典的先有鸡还是先有蛋问题。1970 年代初贝尔实验室的 Ken Thompson 为了玩他自己写的游戏《星际旅行》Space Travel在一台 PDP-7 小型机上搞出了 Unix 的雏形以及一门叫B的语言。B 语言继承自更早的 BCPL但它是无类型的在 PDP-11 上新的字节寻址能力面前显得力不从心。1972 年Dennis Ritchie 在 B 的基础上做了关键改进加入了类型系统这门新语言被命名为C。重点来了第一个 C 编译器并不是用 C 写的。Ritchie 最初用 PDP-11 汇编语言写了一个 C 编译器。但到了 1973 年C 语言已经足够成熟Ritchie 用 C 重写了 C 编译器本身——这就是所谓的自举bootstrapping。同年Unix 内核用 C 进行了大规模重写。这件事意义深远C 证明了一门高级语言可以既强大又高效足以用来写操作系统C 编译器可以用 C 自己来写意味着只要在新机器上实现一个 C 编译器所有 C 代码就都能跑于是 C 成了一把通用钥匙后来所有想做新语言的人第一反应都是先用 C 把它写出来。这就是后面所有故事的源头。二、贝尔实验室的小语言传统sed 与 awkC 在贝尔实验室生根发芽后实验室的大神们很快养成了一个习惯用 C 写各种专注于单一任务的小语言little languages。其中影响最大的两个就是 sed 和 awk。sed1974sed是stream editor流编辑器的缩写由 Lee E. McMahon 在 1973—1974 年间开发。它的思想来自交互式编辑器 ed但 sed 是非交互式的你给它一串编辑命令它就逐行处理文本流非常适合用在管道里。最早的 sed 随 1979 年的 Version 7 Unix 广泛传播整个程序只有大约1600 行 C 代码。别看它小sed 有自己的变量、循环、条件跳转和标签是一门图灵完备的小语言。后来的 Perl 从 sed 这里借鉴了大量正则和文本处理思想。awk1977如果说 sed 擅长编辑那awk擅长的就是提取和报告。它的名字来自三位作者姓氏首字母Alfred Aho、PeterWeinberger、BrianKernighan——这三位都是贝尔实验室的传奇人物Kernighan 后来还和 Ritchie 合写了那本著名的《C 程序设计语言》。awk 同样用 C 写成随 Version 7 Unix 发布。它把文本自动按列切分成字段用类似 C 的语法写规则是有史以来第一门把正则表达式关联数组这些特性带给大众的语言。后来的 Perl、Python、Ruby 身上都有 awk 的影子。和 C 的关系sed 和 awk 都是用 C 实现的领域特定语言DSL语法也明显模仿 C。它们证明了用 C 写一个解释器来跑一种新语言是可行且高效的路子。三、C shell名字就叫C的 shell19781978 年加州大学伯克利分校的研究生Bill Joy后来 Sun 公司的联合创始人vi 编辑器也是他写的在 BSD Unix 上写出了csh。csh 之所以叫C shell有两层含义一是它的语法判断、循环、表达式刻意模仿 C 语言让 C 程序员用起来顺手二是它本身就是用 C 写的。csh 引入了命令历史、别名、作业控制等今天看来理所当然的功能后来的 tcsh 就是它的增强版。和 C 的关系用 C 实现语法也向 C 看齐。它是 Unix shell 家族里和 C 绑定最深的一个。四、给 C 加上面向对象Objective-C19841980 年代初Brad Cox 和 Tom Love 在 ITT 公司接触到了 Smalltalk被它的面向对象思想震撼。但 Smalltalk 太慢、太封闭而他们日常用的是 C 和 Unix。于是 Cox 想能不能把 Smalltalk 的消息传递机制安到 C 身上1981 年前后Cox 写出了一个叫OOPCObject-Oriented Pre-Compiler的预编译器它把带有 Smalltalk 式方括号语法的代码翻译成标准 C 代码再交给已有的 C 编译器编译。1984 年这门语言以Objective-C之名正式发布。Objective-C 是 C 的严格超集——任何合法的 C 程序都是合法的 Objective-C 程序。它后来被 Steve Jobs 的 NeXT 公司采用再随 NeXT 技术回到苹果成为 macOS 和 iOS 二十多年的主力开发语言直到 Swift 出现。和 C 的关系Objective-C 就是 C 加上面向对象层。最早的实现是一个输出 C 代码的预编译器等于把 C 编译器当成了自己的后端。五、C和 C 血缘最近的语言1985说到 C 的后代C 是绕不开的。1979 年Bjarne Stroustrup 在贝尔实验室开始做一个叫“C with Classes”的项目初衷是把 Simula 的类和面向对象特性带到 C 里。1983 年语言正式更名为C名字里的就是 C 的自增运算符。C 的第一个编译器叫Cfront1985 年发布。这里有一个需要特别说明的细节Cfront 的工作方式是把 C 代码翻译成纯 C 代码然后用平台上现成的 C 编译器编译——这和 Objective-C 的思路如出一辙都是蹭C 编译器的可移植性但 Cfront本身是用 C 写的准确说是用C with Classes写的所以它是一个自举编译器而不是用 C 实现的。所以严格来说C 不属于最早用 C 实现的语言但它和 C 的关系是所有语言里最深的它是 C 的超集它的第一个编译器把 C 当作输出目标它的语法、类型、内存模型全部建立在 C 之上。把它请出这篇文章是不可能的所以特别放在这里说明。和 C 的关系C 的直接继承者。Cfront 输出 C 代码但 Cfront 自身用 C 自举——这是一个有趣的边界情况。六、脚本语言大爆发Perl、Tcl、Bash1980 年代末随着 Unix 普及大家需要比 shell 更强大、又比 C 更灵活的胶水语言。一批用 C 实现的脚本语言集中诞生了。Perl1987Larry Wall是个语言学家兼程序员他在 1987 年 12 月 18 日把 Perl 1.0 发布到了 Usenet 新闻组。Perl 融合了 C、sed、awk、shell 的特性正则表达式和文本处理能力极强口号是做一件事不止一种方法There’s more than one way to do it。Perl 的解释器完全用 C 写成核心大约 15 万行 C 代码这让它在当年的各种 Unix 平台上都能轻松编译运行。整个 1990 年代到 2000 年代初Perl 是 Web CGI 编程和系统管理的王者。Tcl1988John Ousterhout在伯克利做集成电路 CAD 工具时发现每个工具都在发明自己一套乱七八糟的命令语言。他决定做一门统一的、可嵌入的命令语言这就是TclTool Command Language1988 年春天诞生。Tcl 解释器用 C 写成以库的形式提供任何 C 程序都能把 Tcl 嵌进去当脚本层。后来 Ousterhout 又给它配了 GUI 工具包TkTcl/Tk 组合一度成为跨平台 GUI 开发的热门选择。今天 Python 自带的 tkinter本质上就是 Tk 的 Python 封装。Bash1989GNU 项目需要一个自由的 Unix shellBrian Fox在 1989 年写出了BashBourne Again SHell双关重生。Bash 完全用 C 实现兼容 sh又加入了命令补全、历史、数组、作业控制等大量功能如今是绝大多数 Linux 发行版和 macOS 的默认 shell。和 C 的关系这三位都是C 实现的解释器的典范。它们用 C 保证性能和可移植性又在 C 之上提供了更高层的抽象——这成了后来几乎所有脚本语言的标准范式。七、互联网时代的四大解释器Python、Ruby、PHP、Lua1990 年代互联网和万维网爆发用 C 写解释器、再用高级语言写业务成了最主流的开发模式。下面四门语言至今仍是各自领域的巨头。Python1991Guido van Rossum1989 年圣诞节期间在荷兰 CWI 研究所开始写 Python1991 年 2 月发布了 0.9.0 版本。Python 的参考实现CPython从第一天起就是用 C 写的——这也是CPython名字里那个C的由来。Python 语法受 ABC 和 Modula-3 影响但花括号、缩进之外的很多底层约定整数、字符串、运算符都能看到 C 的影子。更重要的是CPython 提供了一套完整的C API允许用 C 写扩展模块这让 Python 能轻松粘住各种高性能 C/C 库NumPy 就是典型例子。Ruby1995日本程序员松本行弘Matz想要一门比 Perl 更面向对象、比 Python 更有表达力的语言。1995 年 12 月他公开发布了 Ruby。Ruby 的官方实现叫MRIMatz’s Ruby Interpreter也叫CRuby顾名思义是用 C 写的。它有自己的虚拟机但底层是 C。Ruby 的语法混合了 Perl、Smalltalk 和 C 的风格后来 Ruby on Rails 框架让它在 Web 开发领域大红大紫。PHP1995PHP 的起源特别接地气。1994 年丹麦裔加拿大程序员Rasmus Lerdorf想用几个小程序统计谁看了我的在线简历。他先用 Perl 写了个 CGI 包装器嫌慢又用 C 重写了一遍——这就是 PHP 的雏形。1995 年 6 月 8 日他以Personal Home Page ToolsPHP Tools1.0之名发布到 Usenet。根据 PHP 官方手册的记载PHP 最早的形态就是一组用 C 写的 CGI 二进制程序。后来它加进了表单处理FIForm Interpreter演变成 PHP/FI再由 Andi Gutmans 和 Zeev Suraski 重写引擎发展成今天的 PHP。它的语法大量借鉴 C、Perl是整个 LAMP 时代的 Web 后端之王。Lua19931993 年巴西里约热内卢天主教大学PUC-Rio的三位研究者 Roberto Ierusalimschy、Luiz Henrique de Figueiredo 和 Waldemar Celes需要给巴西石油公司的应用软件配一门可嵌入的脚本语言。当时巴西有严格的计算机硬件软件进口壁垒他们必须自己造。这门语言就是Lua葡萄牙语月亮的意思因为它的前身叫 SOL“太阳”。Lua 从诞生起就坚持一个原则解释器用纯 ANSI C 写成严格遵循 C 标准不依赖任何平台特性。这让 Lua 的可移植性达到了极致——它能跑在从大型服务器到嵌入式设备、甚至游戏主机的几乎任何平台上。《魔兽世界》用 Lua 写插件Redis、Nginx、游戏引擎Cocos2d、LÖVE都把 Lua 当嵌入式脚本。Lua 的 C API 设计得极其干净被公认为C 嵌入脚本语言的教科书。和 C 的关系这四门语言的官方解释器/虚拟机都是用 C 实现的并且都提供了 C 扩展接口。它们语法上不同程度地借鉴 C运行时则把 C 当作底座。可以说C 负责快它们负责好用。八、R统计学家的 C 语言利器19931990 年代初新西兰奥克兰大学的Ross Ihaka和Robert Gentleman想实现一门统计计算语言。他们参考了贝尔实验室的 S 语言做出了一个开源实现这就是R。R 于 1993 年首次公开亮相。它的核心运行时用C 和 Fortran写成统计计算圈有大量经典的 Fortran 数值库这是历史原因并提供了 C/Fortran 扩展接口。今天 R 是数据分析和生物统计领域最流行的语言之一底层大量调用 C 编写的高性能计算代码。和 C 的关系核心用 C加 Fortran实现通过 C 接口复用大量数值计算库。九、JavaScript十天写出来的 C 语言引擎19951995 年网景公司Netscape想让网页动起来。工程师Brendan Eich受命设计一门浏览器脚本语言他花了大约 10 天就写出了原型当时叫 Mocha后改名 LiveScript最终蹭 Java 的热度定名 JavaScript。JavaScript 的第一个引擎也就是后来开源的SpiderMonkey的前身是 Eich用 C 写的。这个引擎包含编译器、解释器、反编译器和垃圾回收器全部是 C 代码。虽然后来 SpiderMonkey 逐步迁移到了 C 和 Rust但 1995 年随 Netscape Navigator 2.0 发布的那第一个版本确实是 C 的产物。JavaScript 的语法花括号、for/while、运算符刻意模仿 Java/C好让当时的 C/Java 程序员觉得眼熟但它的函数式内核其实更像 Scheme。今天 JavaScript 是世界上使用最广泛的语言之一而它的第一个引擎和这篇文章里其他语言一样建立在 C 之上。和 C 的关系第一个 JS 引擎用 C 实现语法表层借鉴 C。注意现代的 V8 引擎是 C 写的但那是后来的事。十、GoC 语言祖师爷晚年用 C 写的新语言2009时间快进到 2007 年Google 的三位大牛——Robert Griesemer、Rob Pike以及当年和 Ritchie 一起发明 C 和 Unix 的 Ken Thompson——对 C 开发大型服务端程序的痛深感不满决定设计一门新语言简单、并发、编译快。这就是Go。这里有一个让很多人意外的事实Go 的初代编译器gc是 Ken Thompson 用 C 写的。2008 年 1 月Thompson 开始动手编译器用 C 编写用 Yacc/Bison 做语法解析连命名都带着 Plan 9 的老传统5c/6c/8c 对应 ARM/x86-64/x86。Go 在 2009 年 11 月 10 日正式开源发布。从 2009 年一直到Go 1.42014 年Go 的编译器、汇编器、链接器全都是 C 代码。到了Go 1.52015 年 8 月Go 团队把编译器和运行时全部用 Go 重写完成了自举——从此 Go 不再依赖 C 编译器。Go 官方在 2015 年的技术演讲Why was it in C? Bootstrapping.为什么当初用 C为了自举。里把这件事讲得明明白白。Go 的语法同样深受 C 影响花括号、分号省略、指针、基本类型甚至 Ken Thompson 本人就是 C 的创造者之一。Go 可以说是 C 语言直系精神传人在 21 世纪的回响。和 C 的关系初代编译器由 C 语言之父之一 Ken Thompson 用 C 写成2015 年自举语法和设计哲学深受 C 影响。十一、遗珠那些同样用 C 写成、值得一提的语言除了上面的主角还有不少语言的第一个版本也是用 C 实现的限于篇幅这里简列m41977Brian Kernighan 写的宏处理语言用 C 实现至今是 Unix/Linux 构建系统autoconf的基石。KornShell / ksh1983David Korn 在贝尔实验室写的 shell用 C 实现融合了 csh 和 sh 的优点是很多商业 Unix 的默认 shell。Zsh1990Paul Falstad 写的 shell用 C 实现功能强大如今是 macOS 默认 shell 和 Oh My Zsh 的基础。Guile1993GNU 项目的官方扩展语言Scheme 的一种实现解释器用 C 写成设计目标就是嵌入到 C 程序里。Plan 9 家族Newsqueak1988、Alef1995、Limbo1995Rob Pike 等人在贝尔实验室用 C 写的并发语言它们的通道channel思想后来直接启发了 Go。Vala2006语法像 C#但编译成 C 代码再用 C 编译器编译编译器本身也用 C 写主要用于 GNOME 生态。Wren、Janet2010 年代两门较新的嵌入式脚本语言都坚持用纯 C 实现解释器延续了 Lua 的哲学。十二、边界情况这些语言和 C 关系很深但最早不是用 C 实现的为了严谨有几个名气很大、容易被误以为是 C 实现的语言需要单独说明C1985如前所述Cfront 把 C 翻译成 C但 Cfront 本身是用 C 自举的。Java1995语法大量借鉴 C/C早期的 Sun JVM 用 C 和 C 编写但 Java 编译器 javac 很早就用 Java 自身实现了属于虚拟机用 C/C、编译器自举的混合情况。C#2000.NET 的 CLR 运行时主要用 C 写C# 编译器 Roslyn 则用 C# 自举。Objective-C最早是输出 C 的预编译器但后来的主流实现GCC/Clang 模块是 C/C 工程。它的特殊之处前面已经讲过。Swift2014Swift 编译器基于 LLVM主要用C写成不是 C。Rust2010Rust 的第一个编译器是用OCaml写的叫 rustboot后来用 Rust 自举。Zig2016Zig 的第一个编译器用C写成2022—2023 年才完成用 Zig 自举。Nim2008最早的 Nim 编译器是用Object PascalDelphi写的后来用 Nim 重写。D2001Walter Bright 最早的 DMD 编译器前端用C写后来才迁移到 D。Julia2012核心用C和 Scheme 写底层用 LLVM不是纯 C。MATLAB1984Cleve Moler 最早的 MATLAB 是用Fortran写的后来才用 C 重写。Erlang1986Joe Armstrong 最早的 Erlang 解释器是用Prolog写的后来的 BEAM 虚拟机才是 C 实现。这些语言有的语法像 C有的运行时依赖 C 的生态但它们的第一行实现并不是 C所以没有进入正文的主表。十三、为什么偏偏是 C讲完这五十多年的故事你可能会问为什么这么多语言的第一个版本都不约而同地选择用 C 来写原因可以归结为四点1. C 是可移植的汇编。C 离硬件足够近能直接操作内存和指针性能和汇编一个量级但它又比汇编高级写起来可移植。只要目标平台有一个 C 编译器你的新语言就能跑。在那个硬件平台五花八门的年代这是巨大的优势。2. C 和 Unix 一起统治了世界。从 1970 年代起Unix以及后来的 Linux的内核、工具链、系统调用全是 C。用 C 写新语言可以无缝调用整个操作系统的能力。C 不只是一门语言它是整个软件世界的接口标准。3. 自举的悖论。写一门新语言时你还没有这门语言的编译器所以必须用一门已有的语言来写它。而 C 几乎是所有平台上唯一一定存在的高级语言。Ken Thompson 后来讲 Go 为什么当初用 C 时答案就两个词“Bootstrapping.”为了自举。4. C 的接口是跨语言的通用插头。几乎所有语言都提供了C 接口C FFI / C API因为 C 的类型和调用约定是所有操作系统和库的事实标准。用 C 实现新语言意味着它能直接调用全世界海量的 C 库。结语从 1974 年贝尔实验室里那 1600 行 C 代码的 sed到 2009 年 Ken Thompson 用 C 敲下 Go 编译器的第一行再到今天仍在用 C 从头实现新语言的 Wren、Janet——C 语言像一条暗河流淌在几乎所有编程语言的脚下。你用 Python 做数据分析、用 PHP 写网站、用 Ruby 做后端、用 Lua 写游戏插件、用 JavaScript 刷网页、用 Go 写微服务——这些语言的语法和哲学千差万别但它们最早的摇篮都是同一种语言。C 已经 54 岁了。很多语言宣称要取代它但讽刺的是这些取代者自己往往还是用 C 养大的。这大概就是 Dennis Ritchie 留给世界最深的烙印C 不只是一门编程语言它是整个数字世界的地基。参考来源Dennis Ritchie《The Development of the C Language》、Bjarne Stroustrup 关于 C 历史的论文、Go 官方博客与 2015 年Go in Go技术演讲、PHP 官方手册、Lua HOPL III 论文《The Evolution of Lua》、Tcl 作者 John Ousterhout 的历史记述、Python/CPython 文档、Ruby 官方站点、Mozilla SpiderMonkey 文档、ACM 与 IEEE Computer Society 相关资料等。