从1999年在Turbo C 2.0下第一次用C语言写出Hello Wolrd以来,不知不觉在IT相关行当里也混了近15年。相信所有上机写过Hello World的人当初都会有这样的好奇心:由键盘敲进去的不过是一个个普通的英文字符,C编译器是如何神奇地发现哪一行出现了什么样的语法错误,更神奇的是编译连接后为什么就能得到一个可以运行的程序,而这个可执行程序又是怎么样在操作系统上运行起来的。这些问题也一直萦绕在我脑海里,挥之不去。大学时所学的《操作系统》和《编译原理》课程似乎对这些问题给出了答案,似乎又没有。很遗憾,那时也没有人告诉我,想比较彻底地搞明白这些问题,最好的办法是去读编译器或操作系统的源代码。因为“计算机科学与技术”这一学科,虽然冠上了“科学与技术”,但实际上还是“技术”的成份要来得多些。在所有强调“技术”的工作中,“技术”通常只能来源于长期的动手实践。即便是开车倒库这样的活,也是个技术活,没有长期动手实践是倒不好车的。纸上得来终觉浅,绝知此事要躬行。而真正的牛人在大学里上完《操作系统》和《编译原理》课程后就已脱颖而出,比如大牛Linus和Chris Lattner,前者大学时就开启了他的Linux操作系统王国,而后者也在研究生期间缔造了如今在业界如日中天的LLVM编译器。Apple公司已经不动声色地把公司的Object-C编译器从GCC转成了LLVM和Clang,各位的IPhone手机中运行的代码可能正是LLVM和Clang的产物呢。Apple新推出的Swift语言背后站着的仍然是Charis Lattner和LLVM。
能写出工业水准的操作系统和编译器,绝对是大牛。而写出来的操作系统和编译器能被工业界普遍接受,则需要命运和机遇的垂青。大部分的程序员注定是成为高级或低级的“码农”,成为软件生产流水线上的一颗螺丝钉。在生活和工作的压力面前,我们往往习惯于不做那么深入的思考,只要代码能完成需求就可以,至于“为什么”的问题往往有意或无意地,甚至被迫去忽略。就如儿时的梦想,只有在夜深人静时,想着年华已去,看着岁月无情,捧着那泛黄的旧照片时,才发现一直以来自己内心不常去的角落中始终留着那个梦想。
操作系统和编译器就如武侠小说中的《九阴真经》,没看过《九阴真经》的侠客也可以行走江湖,但看过并练成九阴真经的人最终才更有机会登上华山之颠。我们很幸运能生活在互联网时代,不必如上世纪70、80年代的欧美程序员那样为了一睹Unix操作系统源代码的风采,私下偷偷复印那本后来被称为旷世奇书的《莱昂氏Unix源代码分析》。其实,真正旷世之作的是那本书中分析的Unix操作系统和用来写Unix的C语言。当然,我们更不必为了一睹九阴真经,而在江湖中掀起血雨腥风。因为九阴真经就赤裸裸地摆在我们的面前,GCC、LLVM和Linux源代码就毫无保留地躺在那。冲动的我们一定曾经兴奋地下载这些源代码,但面对这动不动就几百万行的源代码时,才发现自己是多么的渺小和无力。其实再复杂的系统,其最初的原型往往是不太复杂的,就如早期的Unix操作系统和C编译器,而这些原型往往就是最精髓的部分,包括了最初的直觉和创意。与其面对几百万行的源代码感叹自己”too young, too simple”,不如选择一本浓缩的九阴真经。很幸运,只要我们打开GOOGLE搜索,我们能找到这些原型版的操作系统和编译器。麻省虽小,五脏俱全。
如果我们要分析的是开源的相对完整的C编译器源代码,而不是一个教学用的toy,并且希望源代码的行数在一万行左右,那GOOGLE给我们的答案中就有这么两个,一个是LCC编译器,一个是UCC编译器。如果我们还希望代码结构清晰,且函数名等能望文生义,能直接与C语言的文法吻合,不必去猜测这个函数名是哪几个单词的缩写,那UCC会更胜一筹。当然,UCC的原作者或多或少地受到LCC编译器的影响,但很遗憾,UCC的原作者没有去写本关于UCC的书,他其实是最合适的人选。于是,UCC就如一颗被遗落在角落的珍珠,渐渐地蒙上了尘埃。一万行左右的代码量,UCC就能实现一个基本完整的C编译器,虽然后端只面向32位的X86平台。UCC没有如LCC那样地可变目标,既可生成MIPS,也可生成SPARC和X86代码。虽然偶有BUG,纵然碧玉有瑕,即便不够知名,但只要把UCC这只麻雀解剖清楚,我们已能搞清C编译器是如何工作的,我们已能站在编译器实现的角度来品味那经40余年而愈久愈香的C语言。C和C++语言的相关书籍中,最最经典的如《The C ProgrammingLanguage》、《C++ Primer》和《The C++ programming language》,之所以经典,很大原因是这些作者本身就是C和C++编译器的最早期的实现者。
斗胆越俎代庖去写本关于UCC编译器的书,希望这本书能帮有兴趣读它的人基本搞明白C编译器,更好地用好C语言这个简洁有力的利器。当然,正如一位在编译一线战斗的朋友所言,“UCC和LCC还停留在编译前端和后端的初级阶段”,编译器的优化是现在工业界更加关注的焦点,UCC和LCC在后端优化上都做得不够。不过,站在广大C程序员的角度来看,C编译器的前端才是C编译器与程序员的接口,理解了C编译器前端的实现,就能更深刻地理解C的语法和语义;而C编译器后端,其实是C编译器与CPU的接口,后端的优化为的是在保持语义的前提下,生成速度更快的机器代码。我不是什么牛人,只希望这本书能帮有心人开启一扇看得见、够得着的编译大门,能让C程序员能站在编译器实现的角度来看看自己朝夕相伴的C。全国每年有十万以上的计算机相关专业学生毕业,《编译原理》在大多数毕业生中留下的印象就是很难,很理论化。本书的目的是想把《编译原理》具体化,所谓“伤其十指,不如断其一指”。如果你熟悉C语言,并且有基本的数据结构的知识,那就让我们一起开启C编译器剖析这趟旅程,编译原理和汇编语言相关的知识我们边走边聊,期盼这趟行程结束后,不论你的下一站去何方,你都能说不虚此行。
众里寻她千百度,蓦然回首,She is C。那我们就启程吧。