C语言是用C写的吗?,没有第一个编译器哪来的C?,那它到底从哪冒出来的。
今天翻了翻老资料,才知道这事根本不是传说里那样——C语言不是天才一拍脑袋想出来的,它是被逼出来的。Multics项目搞了几年,一堆大公司参与,结果做出来的东西又慢又占内存,贝尔实验室直接退出。汤普森回到自己办公室,在一台破旧的PDP-7上写了个小系统,连内存都只有8KB。他先用汇编写,写完发现太累,就弄了个叫B的语言,解释执行,简单是简单,但跑得比蜗牛还慢。
后来换到PDP-11,机器好点,B就不行了。里奇开始改——不是加功能,是往硬件上靠。PDP-11有自动增减地址的指令,他就把++和--加进去;寄存器能存16位数据,就定int是16位;内存按字节寻址,char就刚好是1字节。这些不是随便定的,是把机器怎么干活,原样搬进语法里。连a
等于*(a+i)这种写法,都是为了编译器能直接翻译成一条加法指令,不浪费一个时钟周期。
第一个C编译器根本不是用C写的。是里奇他们先用汇编写了个极简版本,只支持if、while、基础运算和函数调用——小到可以在纸上推演清楚。用它编译出稍强一点的C编译器,再用这个编译器去写更完整的C语言和UNIX内核。1973年第三版UNIX内核已经全用C重写了。这过程不是炫技,是实在没别的路:你不能一边造车一边设计轮子,得先用木头轮子跑起来,再换铁的、钢的、最后才是合金的。
很多人说C可移植,其实刚出生那会儿,它根本离不开PDP-11。float自动变double?因为PDP-11的浮点协处理器只算双精度。register关键字后来没人用了?因为编译器自己比人更懂怎么分配寄存器。K&R第一版里main()不写返回类型都行,void也不需要,不是忘了,是当时PDP-11上压根没这个抽象需求。C不是通用语言,它是PDP-11的镜像,只是碰巧长得太像人类写的代码,让人误以为它很“高级”。
1978年那本《The C Programming Language》一出来,大家才发现:哦,原来别人也是这么写的。它没被当成标准,但所有人都照着它干——因为UNIX跟着它一起卖出去了。后来pcc编译器试图把C搬到别的机器上,就得砍掉一些直译硬件的特性,效率降了,但能用了。到1989年ANSI定标准,才正式加入void、函数原型、const这些东西。这不是技术升级,是妥协:C要进大公司、进学校、进课本,就得放弃一部分和硬件的硬连接。
标准化之后的C,慢慢从系统程序员的工具,变成了工程师的通用语言。但它的底子没变:指针还是能随便算地址,数组不检查越界,union还是靠程序员自己记哪个字段在用。这不是bug,是设计选择——它信任你能管住自己,也默认你真正在乎的是机器怎么跑得快,而不是代码看起来多安全。
现在看C语言,它不像Python那样友好,也不像Rust那样防错。但它写出来的程序,只要不写错,就能贴着硬件跑。它的语法古怪,但每一条背后都有一台老机器在喘气。它的编译器能自举,不是为了秀技术,是因为当年没人给它准备现成的工具链,只能自己造一把钥匙,再用这把钥匙打开下一扇门。
C语言没说过它想改变世界。它只是静静地,把1970年代一台小机器的脾气,刻进了全世界的代码里。
它到现在还在用。