原回答(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语言时候的汇编器完全不是同一个东西。
Algol60 -> CPL -> BCPL -> B -> C