读书笔记《程序员的自我修养—链接、装载与库》
第二章 编译和链接
1、gcc编译helloworld程序时的4个步骤:预处理(.i)、编译(.s)、汇编(.o)、链接(可执行文件)。
一、预编译(预处理)过程主要处理那些源代码文件中的以“#”开始的预编译指令。(gcc -E hello.c -o hello.i)比如“#include”、“#define”等,主要处理规则如下:
将所有的“#define”删除,并且展开所有的宏定义。
处理所有条件预编译指令,比如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”。
处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件还可能还包含其他文件。
删除所有的注释“//”和“/* */”。
添加行号和文件名标识,比如#2“hello.c”2,以便于编译时编译器产生调试用的行号信息及用于编译是产生编译错误或警告时能够显示行号。
保留所有的#pragma编译器指令,因为编译器须要使用它们。
二、编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后产生相应的汇编代码文件。($gcc -S hello.c -o hello.s //预编译+编译,C语言对应的预编译和编译程序为cc1)实际上gcc这个命令只是这些后台程序的包装,它会根据不同的参数要求去调用预编译编译程序cc1、汇编器as、链接器ld。
三、汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。($gcc -c hello.s -o hello.o)
四、链接。
2、编译:从最直观的角度来讲,编译器就是将高级语言翻译成机器语言的一个工具。编译过程一般可以分为6步:扫描、语法分析、语义分析、源代码优化、代码生成和目标代码优化。
一、词法分析:首先源代码程序被输入到扫描器,扫描机进行简单地进行词法分析,运用一种类似于有限状态机的算法将源代码的字符序列分割成一系列的记号。
词法分析产生的记号一般可以分为如下几类:关键字、标识符、字面量(数字、字符串等)和特殊符号(如加号、等号)。扫描器同时完成其他工作,比如将标识符存放到符号表,将数字、字符串常量存放到文字表等。(lex程序可以实现词法扫描)
二、语法分析:接下来语法分析器将对由扫描器产生的记号进行语法分析,从而产生语法树。整个分析过程采用了上下文无关语法的分析手段。简单地讲,由语法分析器生成的语法树就是以表达式为节点的树。(yacc可以实现语法分析)
三、语义分析:接下来进行的是语义分析,由语义分析器来完成。编译器所能分析的语义是静态语义(编译期可以确定的语义),与之对应的动态语义是只有在运行期才能确定的语义。
四、源代码优化:现代的编译器有着很多层次的优化,往往在源代码级别会有一个优化过程。源码级优化器会在源代码级别进行优化,往往将整个语法树转换成中间代码(比较常见的有三地址码和P-代码,最基本的三地址码x=y op z //op可以是算数运算,也可以是任何可以应用到y和z的操作)。中间代码使得编译器可以被分为前端和后端,前端负责产生机器无关的中间代码,后端将中间代码转换成目标机器代码。
五、目标代码生成与优化:编译器后端主要包括代码生成器和目标代码优化器。代码生成器将中间代码转换成目标机器代码(依赖于目标机器),最后由目标代码优化器优化,比如选择合适的寻址方式、使用位移来代替乘法运算、删除多余的指令等。
定义其他模块的全局变量和函数在最终运行时的绝对地址都要在最终链接的时候才能确定。所以现代的编译器可以将一个源代码文件编译成一个未链接的目标文件,然后由链接器最终将这些目标文件链接起来形成可执行文件。
3、链接:模块之间如何组合的问题可以归结为模块之间如何通信的问题,最常见的属于静态语言的C/C++模块之间通信有两种方式,一种是模块间的函数调用,另外一种是模块间的变量访问。(归结为一种方式,模块间符号的引用)模块的拼接过程就是链接。
4、人们把每个源代码模块独立地编译,然后按照须要将它们“组装”起来,这个组装模块的过程就是链接。链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确的衔接。从原理上来讲,链接器的工作无非就是把一些指令对其他符号地址的引用加以修正。链接过程主要包括了地址和空间分配、符号决议和重定位等这些步骤。
源代码文件经过编译器编译成目标文件,目标文件和库一起链接形成最终可执行文件。最常见的库就是运行时库,它是支持程序运行的基本函数的集合。库其实是一组目标文件的包,就是一些最常用的代码编译成目标文件后打包存放。
重定位:地址修正的过程。每个要被修正的地方叫一个重定位入口。
小结:在这一章中,我们首先回顾了从程序源代码到最终可执行文件的4个步骤:预编译、编译、汇编、链接,分析了它们的作用及相互之间的联系,IDE集成开发工具和编译器默认的命令通常将这些步骤合并成一步,使得我们通常很少关注这些步骤;
我们还详细回顾了上面这4个步骤中的主要部分,即编译步骤。介绍了编译器将C程序源代码转变成汇编代码的若干个步骤:词法分析、语法分析、语义分析、中间代码生成、目标代码生成与优化。最后我们介绍了链接的历史和静态链接的一系列基本概念:重定位、符号、符号决议、目标文件、库、运行库等概念。