以前学的程序的执行过程是编辑、编译、链接、执行。今天这本书把这个过程更加细化了,它以C语言中的helloworld程序为例进行说明,讲的大概是从编译到链接的过程。
也是包括4步:1、预处理;2、编译;3、汇编;4、链接。从这个顺序可以看出在C语言中预处理是在编译之前。
预编译是个独立的过程,不同于源文件的.cpp格式和头文件的.h格式,预编译得到的文件后缀是.i或者.ii。
预编译的主要动作就是处理代码中以#开头的指令,具体可见P64这些步骤。因为宏已经展开所以.i文件不包含任何宏定义。可以根据.i文件查看宏定义和文件包含是否正确。
预编译需要预编译器。
编译的过程是把预处理得到的文件进行词法分析、语法分析、语义分析和优化后生成相应的汇编代码文件。
汇编阶段是通过汇编器完成的,其作用就是把汇编指令转换成机器指令。汇编结束以后生成目标文件.obj。
链接简而言之就是把目标文件链接在一起生成可执行文件的过程,但是实际上这是一个非常复杂的过程,并不像看上去那么简单。
编译的过程可以分为扫描、语法分析、语义分析、源代码优化、代码生成、目标代码优化等6步。
这一过程是交给扫描器执行的,目的是把程序语句划分成若干记号。
这些记号一般包括:1、关键字;2、标识符;3、字面量(数字,字符串等);4、特殊符号(加号,等号等)。
此外,扫描器还将标识符放到符号表,将字面量放到文字表中以备后用。
词法分析需要此法扫描器。
它是对词法分析产生的各种记号进行语法分析,并产生一颗语法树。
语句内容含义的区分,语法的检查等都是在此阶段完成的。
语法分析需要语法分析器。
语义分析需要语义分析器。
语义分析就是分析该语句的意思,就是它能做什么,有啥用。
编译器所能做的包括静态语义分析和动态语义分析。
静态语义:编译期能够确定的语义,它主要包括类型和声明的匹配,类型的转换等。我想C++中的静态绑定应该也属于静态语义吧。
动态语义:运行期能够确定的语义以及相关问题,比如说异常处理。我同时在想C++中的动态绑定应该属于动态语义。
语义分析对语法树各节点进行了类型标记和类型转换,还更新了符号表里的符号类型。
编译器有很多层次的优化,源码级别的优化是其中一个层次。
源码级的优化需要源码级优化器。
这个优化是把语法树转换成中间代码,并在中间代码上进行的。
常见的中间代码有三地址码和P代码。
中间代码将编译器分成了前端和后端,前端负责产生与机器无关的中间代码,后端负责把中间代码转换成目标代码。
跨平台的编译器并不是放在任意一个平台上都绝对能用,只不过它能支持的平台很多而已。这是因为编译器使用同一个前端,而针对不同的平台使用不同的后端。
编译器的后端包括代码生成器和目标代码优化器。
代码生成器将中间代码转换成目标代码,该过程依赖于目标机器。
目标代码优化器对目标代码进行优化,比如选择合适的寻址方式,以移位代替数乘等。
现在的编译器非常复杂,上述提到的这些方面也变得非常复杂。
变量和函数的地址都是在最终链接的时候才确定的,然后变成可执行文件。
作者把链接比喻为拼图的拼接。
将源代码模块组装起来的过程就是链接。
链接的过程包括:1、地址和空间分配;2、符号决议;3、重定位等。
.obj文件即目标文件和库一起链接成可执行文件。
库是由一些常用的代码编译成的目标文件的包,是一个集合。最常见的库是运行时库,是支持程序运行的基本函数的集合。
每个目标文件都是单独编译的。
模块A想要调用模块B的C函数,A必须要知道C的地址,但是现在A不知道C的地址,但是A给C留了位置,等到链接器链接时再在这个位置上填上C的地址。如果C的地址被改动了,A中所有调用C的地方都需要进行相应的更改,这些都可藉由链接器完成。这是静态链接的基本功能和作用。
在链接的过程中需要对目标文件中定义在其他目标文件中的函数和变量的调用指令进行重新调整,注意这里说的是指令!书中举的例子意在说明,当目标文件A调用目标文件B中的变量C时,因为暂时无法知道C的位置,所以指令先把表示C的位置置为某一值,等到链接的时候再把这值修正为C的地址,这一过程叫做重定位,像C这样的位置被称为重定位入口。