执行一个程序分预处理、编译、汇编和链接。
预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程序源代码进行的处理。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的符号用来支持宏调用。主要的处理规则有:将所有的#define删除,并且展开所有的宏定义;处理所有条件预编译指令,如#if,#ifdef,#elif,#else,#endif;处理#include预编译指令,将被包含的文件插入到该预编译指令的位置,这个过程是递归进行的;删除所有的注释;添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息及错误、警告的行号;保留所有的#pragma编译器指令。
编译过程就是把预处理过的文件进行一系列的词法分析、语法分析、语义分析及优化后生成相应的汇编文件。
汇编器就是将汇编代码转成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。完成后输出object file。
链接是指程序的各模块之间传递参数和控制命令,并把它们组成一个可执行的整体的过程。链接过程包括了地址和空间分配、符号决议和重定位等步骤。静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。把链接这个过程推迟到了运行时再进行,即在可执行文件装载时或运行时由操作系统的装载程序加载库,就是动态链接。链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器或汇编器生成)链接到一块生成可执行程序。
程序源代码编译后的机器指令经常被放在代码段里;全局变量和局部静态变量数据经常放在数据段。其中未初始化的全局变量和局部静态变量放在叫.bss(block start by symbol)段里。总之,程序源代码被编译之后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。数据和指令分段的好处有:程序被装载后,数据和指令分别被映射到两个虚存区域,数据区域可读写,指令区域只读,可分别设置权限,防止恶意篡改;现代CPU被设计成数据缓存和指令缓存分离,所以分开有利于提高对CPU缓存的命中率;当运行多个程序的副本时,只需保存该程序的一份指令。
链接过程的本质就是要把多个不同的目标文件之间相互“粘”到一起,或者说像玩具积木一样,可以拼装形成一个整体。每一个目标文件会有一个相应的符号表,里面记录了目标文件中所用到的所有符号。每个定义的符号有一个对应的符号值,对于函数和变量,符号值就是它们的地址。符号表所含符号有:1)定义在该目标文件中的全局符号,可以被其他目标文件引用。2)在该目标文件中引用的全局符号,但不在该文件中定义,俗称外部符号,如printf。3)段名,往往由编译器产生,值是该段的起始地址。4)局部符号,只在编译单元内部可见。对链接过程没有作用。5)行号信息,即目标文件指令与源代码中代码行的对应关系,可选的。