第二章 编译和链接
2.1 被隐藏了的过程
编译运行主要分解为4个步骤:
预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking).
2.1.1 预编译
预编译处理规则:
1、 讲所有的“#define”删除,并且展开所有的宏定义。
2、 处理所有条件预编译指令,比如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”。
3、 处理“#include”预编译指令,将包含的文件插入到该预编译指令的位置。注意,设个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
4、 删除所有的注释“//”和“/**/”.
5、 添加行号和文件名标识,比如#2“hello.c” 2,以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时能够显示行号。
6、 保留所有的#pragma编译指令,因为编译器需要使用它们。
编译指令:
$gcc –E hello.c –o hello.i
或
$cpp hello.c > hello.i
2.1.2编译
编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。
编译指令:
$gcc –s hello.i –o hello.s
2.1.3 汇编
汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。
编译指令:
$as hello.s –o hello.o
或者:
$gcc –c hello.s –o hello.o
或者:经过预编译,编译和汇编直接输出目标文件。
$gcc –c hello.c –o hello.o
2.1.4 链接
把一堆的.o文件链接起来,生成.out的可执行文件。
源码:后面都将以此为例
Array[index] = (index + 4) * (2 + 6)
2.2 编译器做了什么
编译过程分为6步:扫描、语法分析、语义分析、源代码优化、代码生成、目标代码优化。如图2-2:
图2-2 编译过程
2.2.1 词法分析
将源代码程序输入到扫描器,扫描器进行词法分析,运用一种类似于有限状态机的算法可以很轻松的将源代码的字符序列分割成一系列的几号。
词法分析产生的记号一般可以分为以下几类:关键字、标识符、字面量(数字、字符串)和特殊符号(加号、等号等)。
可使用工具:lex程序
2.2.2 语法分析
语法分析器将扫描器产生的记号进行语法分析,从而产生语法树,整个分析过程采用了上下文无关法的分析手段,简单的讲,由语法分析器生成的语法树就是以表达式为节点的树。如图2-3:
语法分析工具:yacc
图2-3 语法树
2.2.3 语义分析
编译器所能分析的语义是静态语义。静态语义是指在编译器可以确定的语义,与之对应的是动态语义,就是只能在运行期才能确定的语义。
静态语义通常包括声明和类型的匹配、类型的转换。语义分析后结果如图2-4:
图 2-4 标识语义后的语法树
2.2.4 中间语言生成
源码级优化器:在源代码级别进行优化。如将(2+6)表达式优化掉。一般是将整个语法树转换成中间代码,它是语法树的顺序表示。如图2-5
中间代码:接近目标代码,不同的编译器有不同的形式,常见的:三地址码,P-代码。
中间代码使得编译器可以分为前端和后端,编译器前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。
图2-5 优化后的语法树
2.2.5 目标代码生成与优化
源代码级优化器产生中间代码标志着下面的过程都属于编译器后端,编译器后端主要包括:代码生成器和目标代码优化器。
代码生成器:将中间代码转换成目标机器代码,依赖于目标机器。
目标代码优化器:对目标代码进行优化,比如选择合适的寻址方式,使用位移来代替乘法运算、删除多余的指令等。
至今没有一个编译器能够欧完整支持C++语言标准所规定的所有语言特性。
编译完成,index和array的地址还没确定,需要使用链接器进行链接。
现代的编译器可以将一个源代码文件编译成一个未链接的目标文件,然后由连接器最终将这些目标文件链接起来形成可执行文件。
2.3 链接器年龄比编译器长
重定位:重新计算各个目标的地址过程
符号:表示一个地址,这个地址可能是一个子程序(函数)的起始地址,也可以是一个变量的起始地址。
最初程序是一个模块,后来功能越多,代码越来越多,复杂度增加,所以拆分为多个模块,在拆分为多个模块以后,这些模块之间最后如何组合形成一个单一的程序,模块之间如何组合的问题可以归结为模块之间如何通信,最常见的属于静态语言c/c++模块之间通信有两种方式:一种是模块间的函数调用,另一种是模块间的变量访问,函数访问须知道目标函数的地址,变量访问也须知道目标变量的地址,这两种方式都可以归结为一种:模块间的符号引用。
链接:各个模块的拼接过程。
2.4模块拼装---静态链接
链接:每个源代码模块独立编译,然后按照需要将他们“组装”起来。
链接的主要内容:把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确的衔接。
链接的过程:地址和空间分配,符号决议,重定位.
符号决议:也叫做符号绑定,名称绑定,或者地址绑定,指令绑定。决议更倾向于静态链接,而绑定更倾向于动态链接。
重定位:假设A和B链接后,变量var的地址确定下来为0x1000,那么链接器将会把这个指令的目标地址部分修改为0x1000,这个地址修正的过程叫做重定位。每个要被修正的地方叫一个重定位入口。重定位要做的就是给程序中每个这样的绝对地址引用的位置“打补丁”,使他们指向正确的地址。
库 :一组目标文件的包,就是一些最常用的代码编译成目标文件后打包存放。
运行时库:一种被编译器用来实现编程语言内置函数,以提供该语言程序运行时(执行)支持的一种特殊的计算机程序库
本章总结
从程序源代码到最终可执行文件的4个步骤:预编译,编译,汇编,链接。
C程序源代码转变成汇编代码的若干个步骤:词法分析,语法分析,语义分析,中间代码生成,目标代码生成与优化
静态链接的一些基本概念:重定位,符号,符号决议,目标文件,库,运行库。