程序员的自我修养总结(二)
声明:引用请注明出处http://blog.csdn.net/lg1259156776/
说明:这是程序员的自我修养一书的读书总结,随着阅读的推进,逐步增加内容。
由源文件到可执行文件
分为四个步骤:
预处理
处理源代码中以#开始的预编译指令,进行宏定义展开,处理所有条件预编译指令,将被包含文件插入到预编译指令的位置,删除所有注释,添加行号及文件标识,保留#pragma编译器指令,因为编译器需用到。
编译
进行一系列词法分析,语法分析,语义分析及优化后生成汇编代码文件。
汇编
将汇编代码转变为机器可执行的指令,生成的是目标文件
链接
将目标文件以及所包含的库文件等链接到一起生成最终的可执行文件。
编译器的具体工作
从源代码开始,输入到扫描器(scanner)中,进行词法分析(运用类似有限状态机的算法),分割为一系列的记号(Token)。然后进行语法分析,产生语法树,采用上下文无关语法的分析手段。之后进行语义分析,由语义分析器完成,语法分析仅仅完成了对表达式的语法层面上的分析,但并不了解语句是否真正有意义。编译器能分析的语义是静态语义,在编译期可以确定的语义。而动态语义只有在运行期才能确定。静态语义通常包括声明和类型匹配,类型转换。动态语义一般指在运行期出现的语义相关的问题,比如讲0作为除数是一个运行期语义错误。然后是中间语言生成,即源码级优化器,在原代码级别进行优化,此时仍然是与目标机器和运行时环境无关的,并不包含数据尺寸、变量地址和寄存器的名字。中间代码形式很多,常见的有三地址码,表示两个地址上的数据进行某项操作,赋值给另一个地址上。中间代码使得编译器可以被分为前端和后端,编译器前端负责产生与机器无关的中间代码,后端负责将中间代码转换为目标代码,所以对于一些跨平台的编译器而言,他们可以针对不同的平台使用同一个前端和针对不同机器的数个后端。之后是代码生成器和目标代码优化器,对应的都是编译器的后端,代码生成器将中间代码转换为目标机器代码,此过程十分依赖目标机器,因为不同的机器有着不同的字长、寄存器、整数数据类型和浮点数据类型等。最后目标代码优化器对目标代码进行优化,选择合适的寻址方式,使用位移代替乘法,删除多余指令等。这个目标代码中变量地址还没有确定,而且如果函数或者变量是定义在其他模块呢时呢?定义在其他模块的全局变量和函数在最终运行时的绝对地址都要在最终链接的时候才能确定。现代编译器可以将一个源代码文件编译成一个未链接的目标文件,然后由链接器最终将这些目标文件连接起来形成可执行文件。
重定位,符号,用来表示一个地址,既可能是一个变量的起始地址,也能是一段代码或者函数的起始地址。访问函数需要知道目标函数的地址,访问变量需要知道目标变量的地址,两种方式可以归结为一种方式,那就是模块间符号的引用,依靠符号来通信,类似拼图版,定义符号的模块多出一块区域,引用该符号的模块刚好少了那一块,两者的拼接刚好完美组合,这个拼接过程就是“链接”。
模块拼装之静态链接
链接的主要内容是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确链接。
主要包括:地址和空间分配,符号决议和重定位等步骤。
符号决议也被叫做符号绑定,名称绑定,名称决议,地址绑定,指令绑定等。决议更倾向于静态链接,绑定更倾向于动态链接。
最基本的静态链接过程:
每个模块的源代码经过编译器编译成目标文件(.o或.obj),目标文件和库一起链接形成最终可执行文件。最常见的库就是运行时库,runtime library,指的是支持程序运行的基本函数的集合。库其实是一组目标文件的包,就是一些最常用的代码编译成目标文件后打包存放。
编译器编译的时候每个模块都是单独编译的,并不知道具体调用的函数入口地址,使用链接器可以直接引用其他模块的函数和全局变量而无需知道它们的地址,因为链接器在链接的时候会根据使用的符号,自动去相应的目标文件中查找对应的地址,然后将引用到的符号地址重新修正,这就是静态链接的最基本的过程和作用。地址修正的过程被叫做重定位Relocation,每个被修正的地方叫一个重定位入口Relocation Entry。重定位所做的就是给程序中每一个这样的绝对地址引用的位置打补丁,是他们指向正确的地址。
2015-10-22 读书笔记 张朋艺