同学们总是抱怨每次见到一道面试题都很难把它转化为程序源代码。然而不幸的是,即使是程序源代码对于计算机来说也还是太高级了。要想让计算机执行一段程序,我们必须把它翻译成最底层的机器指令才行。这其中要经历很多步骤。幸运的是有很多现成的工具可以帮助我们完成这个过程。今天我们就以 C 语言为例给大家做个简单介绍。(Java 还要更复杂一些,涉及到字节码和虚拟机,今天就不讲了。)
预处理(Preprocessing)
翻译一段 C 语言程序的第一步是预处理。这一步主要处理所有以“#”号开头的行。比如当我们遇到 #include "header.h"
的时候,就直接把 header.h
文件里的所有内容插入到这儿。由此可见,一段 C 语言程序经过预处理之后还是一段 C 语言程序。
编译(Compilation)
第二步是编译,也就是将一段 C 语言程序翻译为一段汇编语言程序。汇编语言比 C 语言要底层多了,基本上就是机器指令的文字版,每一行汇编语言对应着一条机器指令。往往 C 语言程序里的一行要被翻译成好几行汇编语言程序才行。不同的高级语言(如 C、Fortran、Pascal 等等)有不同的预处理器和编译器,但它们在经过编译之后都变成了同一种汇编语言,于是后面的步骤和工具大家都是通用的了。
汇编(Assembly)
第三步是汇编,即把文字版的汇编语言程序真正翻译成由 0 和 1 组成的机器指令,并把它们打包输出成一个 relocatable object program(下文简称目标文件)。如果说汇编语言程序人类还勉强能看懂,经过这一步之后得到的目标文件就真的是一堆二进制乱码了。
链接(Linking)
第四步是链接,用来把多个目标文件合并成一个可执行文件。比如说我们写了一个 hello world 程序,其中调用了 C 语言标准库里的 printf() 函数来打印。我们的 hello world 的主函数和 printf() 函数存放在两个不同的目标文件里,经过这一步之后它们就被合并到一起了。(这里讲的是静态链接。如果是动态链接的话,printf() 在这一步暂时不合并进来,而是在下一步程序运行的时候才被加载。)
加载(Loading)
上一步所生成的可执行文件终于可以被操作系统加载运行了。操作系统会将这个可执行文件中的代码和数据从磁盘复制到内存中,并跳转到该程序的第一条指令处(也叫做入口点,entry point)开始执行。
最后用一张图给大家总结一下全过程:
(本文在写作过程中参考了 Randal E. Bryant 和 David R. O'Hallaron 所著的 Computer Systems: A Programmer's Perspective 第三版。)
更多北美CS求职信息请至:http://www.laioffer.com