一个源程序到一个可执行程序一般需要经过预编译、编译、汇编和链接四个步骤。当我们使用IDE编写代码的时候,这些过程IDE都会默认的为我们完成。但是了解这些过程都是做什么是很有必要的。
1、预编译
预编译过程主要处理源文件中以“#”开始的预编译指令。比如“#include”,“#define”等,主要处理规则如下:
将所有的“#define”删除,并且展开所有的宏定义
处理所有的条件预编译指令,比如“#if”,“#ifdef”,“#endif”
处理“#include”指令,将被包含的文件插入到该预编译指令的位置,注意这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件
删除所有的注释“//”、“”
添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息以及用于编译时产生的编译错误或警告时显示的行号
保留所有的#program编译器指令
进过预编译后,生成的文件不包含任何的宏定义。
2、编译
编译过程就是把预处理完的文件进过一些列的词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。
3、汇编
汇编就是将汇编代码转变成机器可执行的指令,得到目标文件。
4、链接
链接及时将目标文件进行处理,得到可执行文件。
下面主要介绍链接的相关知识:
因为现在的工程越来越大,所以人们将代码按照功能和性质划分,分别形成不同的功能模块,不同的模块之间按照层次结构或者其他结构来组织。
在上面介绍的前三步中,每一个每一个模块都是单独进行的,不需要其他模块。但是在链接构成中,不同的模块之间就可能需要通信。在前面的步骤中,当需要引用到其他模块的内容的时候,暂时使用一些标记来标识这些引用的地址。
链接的主要内容就是把各个模块之间相互应用的部分处理好,使各个模块之间能够正确的衔接(这部分会替换前面使用的那些标识地址的内容,该文真正的地址)。
链接方式分为静态链接和动态链接:
静态链接:静态链接在链接时将输入的目标文件进行合并操作形成可执行文件。
使用静态链接,链接时将相关的目标文件合并为一个可执行文件。
动态链接:动态链接就是将链接过程推迟到运行时在进行。
为什么需要动态链接呢?
因为如果我们使用静态链接,那么对于每一个可执行程序内都会包含与其相关的目标代码,这样就可能会造成浪费内存和磁盘空间,模块更新的困难。
比如说现在有两个程序program1和program2分别包含program1.o和program2.0两个模块,同时好共用Lib.o模块。在静态编译情况下,因为program1和program2都用到了lib.o米快,所以它们同时在链接输出的可执行文件program1和program2有两个副本。当我们同时运行这两个可执行文件时,Lib.o在磁盘和内存中就有两个副本。这样就会造成内存和磁盘的浪费。
此外使用静态链接还会造成程序发布和更新的问题。比如program1使用了Lib.o,当Lib.o更新后,program1必须重新进行编译和链接。这样的缺点就是一个很小的模块更新,整个程序都需要从新链接、发布。
使用动态链接皆可以避免以上两个问题。当然动态链接也有相应的问题,比如模块更新后借口改变了,那么原来的程序就无法运行了。
所以动态链接和静态链接都有优点和缺点。
//----------------------------------------------------------------------------------
在多道程序环境中,要想将一个用户源代码变成一个可以在内存中执行的程序,通常分为三个步骤:编译、链接、载入。
(1)编译:由编译程序将用户的源代码编译成若干个目标模块。
(2)链接:由链接程序将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的载入模块。
(3)载入:由载入程序将载入模块载入内存。
编译和链接都是为将用户程序从硬盘上调入内存并将其转换为可执行程序服务的。用编译器时的compile就是在进行编译,link就是链接,运行程序时可以看到。
编译可以理解为高级语言翻译为计算机可以理解的二进制代码,即机器语言。
链接可以举例解释:一个程序编译后,在作业地址空间中所得到目标模块的起始地址通常是0,假设在1000处有一条指令load 1, 2500,即将2500单元处的数据取至寄存器1中。在多道程序环境下,将程序载入内存时并不可能预先知道所编译的模块应该放在内存的何处。假设程序被载入从10000开始的地址,此时的2500就应该变成了12500,这里就出现问题了,需要修改指令中的相对地址,但此时如果采用动态链接就可以不用修改地址,灵活地解决这个问题了。
链接分三种:静态链接、载入时动态链接、运行时动态链接,现在流行的是运行时动态链接,这种不仅可以回忆程序的载入过程,而且节省了大量的内存空间。
再来看C/C++的编译和链接:
无论是C/C++,首先要把源文件编译成中间代码文件,在Windows下面就是.obj文件,Unix、Linux下面就是.o文件,即Object File,这个动作叫编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。
编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(.o文件或是.obj文件)。
链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(.o文件或是.obj文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是.a文件。
总而言之,链接就是那些目标文件之间相互链接自己所需要的函数和全局变量,而函数可能来源于其他目标文件或库文件。
总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现,需要指定函数的Object File。
