3、程序过程
3.1 预处理
预编译步骤主要操作源代码文件中以“#” 开始的预编译指令
gcc -E hello.c -o hello.i
a) 将所有的 “#define” 指令删除,并展开内容中的宏定义
b) 处理所有条件预编译指令,如 “#if”等
c) 处理“#include” 预编译指令,将被包含的文件插入到该预编译指令的位置,递归包含
d) 删除所有注释行
e) 添加行号与文件名标识
f) 保留所有#pragma 编译器指令
3.2 编译
$gcc -S hello.i -o hello.s
$gcc -S hello.c -o hello.s
编译过程经历了扫描器(词法分析),语法分析,语义分析,源代码优化,产生目标代码,目标代码优化的过程,产生对应的汇编代码文件
3.2.1 扫描器(词法分析)
源代码被输入到扫描器中,扫描器进行简单的词法分析,运用有限状态机的算法,将源代码分割为一系列的记号(关键字,标识符,字面值和特殊符号<+,-,*,/等>)。有一个叫做 len 的程序可以实现词法扫描,按照用户之前描述好的词法规则将输入的字符串分割为一个个记号
3.2.2 语法分析
对扫描器产生的记号进行语法分析,从而产生一棵语法树,其叶子节点通常是标识符或者字面值。有一个叫yacc的工具,可以根据用户定义的语法规则对输入的记号序列进行解析,构建出一棵语法树
3.2.3 语义分析
语法分析完成了表达式语法层面的解析,但它并不了解这个语句是否有意义,如 C语言中,两个指针做乘法运算时没有意义的,但是这个语句在语法层面是合法的。编译器只能进行静态语义分析(如类型转换,声明匹配等),不能进行动态语法分析(如除数为0的除法操作)。经过语义分析阶段后,整个语法树的表达式都被标识了类型,如果存在类型转换,还会在语法树中插入相应的转换节点。
3.2.4 源代码优化
源代码优化器将整个语法树转换为中间代码(如三地址代码等),并对三地址代码进行优化。
中间代码使得编译器可以被分成前端和后端,编译器前端负责产生与机器无关的中间代码,编译器后端将中间代码转换为机器代码。
3.2.5 产生目标代码
代码生成器将中间代码转换成目标机器代码(汇编语言),这个过程依赖与不同的主机,属于编译器的后端部分。因此,对跨平台的编译器而言,可以针对不同的平台使用同一个前端,而使用不同的对应平台的后端
3.2.6 目标代码的优化
这个是对产生的汇编代码进行优化,此处不涉及汇编,不举例了
3.3 汇编
$gcc -c hello.s -o hello.o
$gcc -c hello.c -o hello.o
汇编器将汇编代码转换为机器可以执行的指令,每一个汇编语句几乎对应一条机器指令。该过程相对简单,只是根据汇编指令和机器指令的对照表一一翻译即可
3.4 链接
其不同的模块(源文件)之间的通信方式有两种:一种是模块间的函数调用,另一种是模块间的变量访问
编译器在编译到目标文件的过程中,将引用别的模块中的函数与变量的地址都暂时搁置(设为0x00000000),等待链接器对这些模块进行链接时将这些含有引用的指令进行修正,调整至正确的地址。
这个地址修正的过程被称为重定位,而每一个要被修正的地方称为重定位入口。链接过程包括了地址和空间分配,符号决议与重定位等步骤
3.4.1 静态链接
在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。(个人备注:静态链接将链接库的代码复制到可执行程序中,使得可执行程序体积变大)
3.4.2 动态链接
动态链接出现的原因就是为了解决静态链接中提到的两个问题,一方面是空间浪费,另外一方面是更新困难。
在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。(个人备注:动态链接指的是需要链接的代码放到一个共享对象中,共享对象映射到进程虚地址空间,链接程序记录可执行程序将来需要用的代码信息,根据这些信息迅速定位相应的代码片段。)
3.5 存在错误
编译:语法错误(缺少命名空间、变量未定义、括号、类型转换、缺;)
链接:缺少库文件、
运行:数组越界访问、堆栈溢出