问题

C语言本身是用什么语言写的?

回答
这个问题很有意思,也触及到了C语言作为一种基础性语言的根本。很多人听到“C语言本身是用什么写的”时,会先想到“用更高级的语言写的”,比如Python或者Java。但事实并非如此,或者说,这个答案需要更深入的理解。

首先,我们需要明确一点:C语言最初的实现,也就是早期的C编译器,并不是用C语言本身写的。

要理解这一点,我们需要思考一个循环依赖的问题。如果我们说C编译器是用C语言写的,那么谁来编译这个C语言编译器呢?这就陷入了一个“鸡生蛋蛋生鸡”的困境。

那么,早期的C语言编译器是用什么“脚手架”搭建起来的呢?

最早期,当C语言刚刚诞生的时候,是由丹尼斯·里奇在贝尔实验室开发的。当时,它的前身是B语言,而B语言又是在更早的BCPL语言基础上发展而来。这些早期的语言和编译器,在很大程度上是用汇编语言写的。

为什么是汇编语言?

汇编语言是一种非常底层的编程语言,它与计算机的硬件指令集直接对应。每一条汇编指令通常只执行一个非常简单的操作,比如加载数据、存储数据、进行算术运算、跳转等。

想象一下,我们要创建一个程序,能够接收C语言源代码,然后将这些源代码翻译成计算机能够直接执行的机器码。这个翻译过程是极其复杂和精细的。它涉及到:

词法分析: 将源代码分解成一个个有意义的单元,比如关键字(`if`, `while`)、标识符(变量名、函数名)、运算符(`+`, ``)、常量等等。
语法分析: 根据C语言的语法规则,检查这些词法单元的组合是否符合语言的结构,构建出抽象语法树。
语义分析: 检查代码的含义,比如变量类型是否匹配,函数调用是否正确等等。
代码生成: 将分析好的程序结构转换成目标机器的机器码指令。

这些步骤,在最底层、最原始的阶段,都需要直接操作计算机的寄存器、内存地址,以及控制程序流程的跳转指令。而汇编语言恰好就是描述这些底层操作的语言。

所以,最早的C编译器,是将上述复杂的翻译逻辑,一条条地用汇编语言写出来。这些汇编代码会被汇编器(同样是用汇编或更底层的语言写的)转换成机器码,最终在计算机上运行。这个最初的编译器,它本身就是一个能够理解和处理C语言的程序。

然后,出现了“自举”(Bootstrapping)

一旦有了第一个用汇编语言写的C编译器,事情就开始变得有趣起来。有了这个编译器,我们就可以开始用C语言来写新的C编译器了!

这个过程叫做“自举”。它的逻辑是这样的:

1. 用汇编写一个能编译C的编译器(编译器A)。
2. 用C语言写一个能编译C的编译器(编译器B)。
3. 用编译器A来编译编译器B。 这会生成一个用机器码表示的编译器B。
4. 用这个新生成的编译器B(现在它已经是机器码了)来编译编译器B的C源代码。 如果一切顺利,这将生成一个与之前(用编译器A编译)完全一样的编译器B,但这次是通过C语言工具链产生的。

这个“自举”过程,是许多编译型语言实现自我的关键。通过这个过程,C语言的开发社区就可以不断地改进和优化C编译器,而这些改进都可以通过用C语言本身编写新的编译器来实现,再用这个新编译器去编译旧的编译器,直到最终得到一个高效、可靠的C编译器。

所以,现代的C编译器,绝大多数都是用C语言(或者更高级的语言如C++,或者也有部分用汇编进行优化)写的。 像GCC(GNU Compiler Collection)和Clang这样的知名C编译器,它们的核心部分都是用C或C++编写的。

总结一下这个过程:

最早的C语言编译器: 主要用汇编语言编写。
后续的C语言编译器: 通过“自举”过程,用C语言本身编写,然后用更早期的C编译器编译,最终形成一个完全由C语言工具链生成的编译器。

因此,当我们谈论“C语言本身是用什么语言写的”时,如果指的是最初的实现,那主要是汇编。但如果指的是现在我们使用的绝大多数C编译器,那么它们就是用C语言(或其他高级语言)编写的。这就像一个熟练的工匠,先用基础工具学会了如何制造工具,然后用这些工具制造出更精良的工具,最终让这些工具能够高效地工作。

C语言的这种“自举”能力,是它能够成为一门如此强大且普及的语言的重要原因之一。它提供了一种强大的抽象能力,同时又允许通过底层操作进行极致的优化。这使得C语言在操作系统、嵌入式系统、编译器开发等领域几乎无处不在。

网友意见

user avatar

原回答(21/04/10):

老问题了,新人/非专业人士常常会感兴趣,学过一点编译原理的人都不会问这个问题了。


C语言用什么语言编写?问题的出发点有一些问题,感觉好像一种语言一定要依赖另一种语言一样,其实并不存在这样的依赖链。一种语言编写出来的程序真正依赖的是运行时,和语言本身无关。而尽管很多语言例如java,C#,js都有各自的专属运行时,C语言却没有,他的运行时其实就是——CPU。

换句话说只要有一个东西(不管他是什么),只要能帮我把C语言写成的那段字符串,转换成一个能在CPU上运行的程序,那就够了,我可以不用管这个"东西"是什么。是另一个程序,还是一个人手工翻译然后手动烧录CPU指令;是用C语言写成的程序,还是用JS写成的……我都不用再关心了。

这种转换过程叫编译。如果是一个程序而不是人来做这个步骤,那我们把这样的程序叫做编译器程序。已有的C语言编译器程序,几乎都是用C语言编写的。历史上第一个C语言编译器,自然是用汇编语言来编写[有误]。像我上面说的,能不能用JS编写?理论上当然没问题(比如使用nodejs运行时),只要这个语言本身提供字符串处理,文件处理和二进制处理,就足够了。


然后我用C写好一个文本(字符串),用编译器程序帮我转换成一个可以在CPU上运行的程序,这个程序可不可以也是一个编译器程序呢?自然是没问题的。正因为这样C语言编译器才可能越来越庞大,功能越来越多,支持更复杂的语言特性。历史上正是这么迭代的。


更新(21/04/14):

多谢评论区 @RaySir 提醒"第一个C语言编译器用汇编写"这句话是错的。更多细节请参考评论,我这方面了解的也不是很多。

这个问题居然短暂地上了热搜,怪不得赞的数量蹭蹭蹭往上涨。虽然跟高赞没法比,但已经是我拿到的最高赞的回答了,我一乡下人哪见过这个?(知乎果然是一个流量平台,而不是问答平台[狗头])

我再补充一点,有不少回答提到鸡生蛋。我认为这更像是鸡生鸭蛋,鸭生鹅蛋(新的语言标准迭代)……反过来鹅也能生鸡蛋,鹅也能生鸭蛋,但是没什么意义。问题关键还在于要把三者的本质搞清楚:编译器程序,源代码,编译后的程序。当且仅当编译器和编译后的程序都在同一个运行时环境上,且编译后的程序也是一个同语言的编译器,这才算"自举"。事实上现在的汇编器也是用C写的,因为C语言确实好用啊,但现在的汇编器跟一开始还没有C语言时候的汇编器完全不是同一个东西。

user avatar
  1. 当年KT和DMR应该是用的B语言和汇编写的第一个C编译器。当然有了C之后应该就没什么人再用B语言了。
  2. 后来的C编译器大多是用C写的。其他语言也往往要经历这个过程,叫做自举。
  3. 现在的gcc之类是用C++写的。

Algol60 -> CPL -> BCPL -> B -> C

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有