一个程序的生命周期是从一个源程序(或者说源文件)开始的
即程序员通过编辑器创建并保存的文本文件。
源程序实际上就是一个由0和1组成的位(又称比特)序列,8个位被组织成一组,称为字节。每个字节表示程序中的某些文本字符。
像 .c 这样只由ASCII字符构成的文本称为文本文件
,其他所有的文件都称为二进制文件
。
为了在系统上运行这个程序,每条C语句都必须被其他程序转化为一系列的低级机器语言指令
,然后这些指令按照一种为可执行目标程序的格式打好包,并以二进制磁盘文件的形式存放起来。目标程序也称为可执行目标文件
。
从一个.c/.cpp的文本文件到一个.exe的可执行程序一般被分为四个阶段
预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)、链接(Linking)。
执行这四个阶段的程序(预处理器、编译器、汇编器、和链接器)一起构成了编译系统。
1.预处理
.c -> .i
1.预处理器(cpp)将所有的#define删除,并且展开所有的宏定义。
2. 处理所有的条件预编译指令,比如#if、#ifdef、#elif、#else、#endif等。
3.处理#include预编译指令,将被包含的文件所有内容直接插入到预编译指令的位置。 删除所有的注释。
4. 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
5.保留所有的#pragma编译器指令,因为编译器需要使用它们。
2.编译
编译器(ccl)将预处理完的文本文件 .i
文件进行一系列的词法分析、语法分析、语义分析和优化,翻译成文本文件 .s
,它包含一个汇编语言程序。
编译过程可分为6步:词法分析、语法分析、语义分析、源代码优化、代码生成、目标代码优化。
词法分析:扫描器(Scanner)将源代的字符序列分割成一系列的记号(Token)。lex工具可实现词法扫描。
语法分析:语法分析器将记号(Token)产生语法树(Syntax Tree)。
语义分析:静态语义(在编译器可以确定的语义)、动态语义(只能在运行期才能确定的语义)。
源代码优化:源代码优化器(Source Code Optimizer),将整个语法书转化为中间代码(Intermediate Code)(中间代码是与目标机器和运行环境无关的)。中间代码使得编译器被分为前端和后端。编译器前端负责产生机器无关的中间代码;编译器后端将中间代码转化为目标机器代码。
目标代码生成:代码生成器(Code Generator).
目标代码优化:目标代码优化器(Target Code Optimizer)。
3.汇编
.s ->>.o/.obj
汇编是将汇编代码转换为机器码的过程。汇编器会读取汇编代码,并将其转换为机器指令、数据区等内容。在汇编过程中,会生成符号表、重定位表等信息,以便后续的链接操作使用。
4.链接
.o/.obj ->>>>>.exe
链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。
链接可以执行于编译时
,也就是在源代码被翻译成机器代码时;
也可以执行于加载时
,也就是在程序被加载器加载到内存并执行时;
甚至执行于运行时
,也就是由应用程序来执行。
链接是由叫链接器(linker)的程序自动执行的。
我们知道一般一个项目中会有很多很多的.cpp .h 还包括其他的第三方的一些库等等其他一切程序在执行中需要用到的东西,
上述的预编译、编译、汇编,只是将一个个的.cpp 或者 .c 文件编译成 .o 二进制文件,这些还不能直接执行,就像是一个汽车,虽然把每个部件都已经生产好了,但是没有组装,只有将所有零件整合起来,这样这个汽车才能跑起来。
链接的工作就是将该程序相关的所有文件都链接起来
链接一般可以分为静态链接和动态链接,也就是我们所说的动态库、和静态库。
库就是已经编译好的二进制文件。它包含着一些已经封装好的函数。比如说,我们在.c文件中include了 stdio库,就可以利用 printf 函数进行输出,而不用自己手写 输出函数。
一般使用库就是两种情况
一种情况是某些代码需要给别人使用,但是我们不希望别人看到源码,就需要以库的形式进行封装,只暴露出头文件。
另外一种情况是,对于某些不会进行大的改动的代码,我们想减少编译的时间,就可以把它打包成库,因为库是已经编译好的二进制了,编译的时候只需要Link 一下,不会浪费编译时间。
静态库的链接
把调要调用的函数或者过程直接链接到可执行文件(dll或exe)中,成为可执行文件的一部分。该执行文件中包含了运行所需的全部代码(也就是说相当于把静态库中的全部代码拷过来了一样)。
优点: 链接该静态库的可执行文件(dll或exe等)使用时,无需再需要该静态库。
缺点: a)当多个程序都要调用相同函数时,内存中就会存在这个函数的多个复制,存在资源浪费。
b)当静态库发生修改时,不仅该静态库要从新编译,引用该静态库的模块都需要从新编译。
2、动态库的链接
动态链接调用的函数代码并没有被复制到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息
(往往是一些重定位信息)。
仅当应用程序被装入内存开始运行时,在操作系统的管理下,才在应用程序与相应的动态链接库(dynamic link library,dll)之间建立链接关系。
当要执行调用.dll文件中的函数时,根据链接产生的重定位信息,操作系统才转去执行.dll文件中相应的函数代码。
优点: 当修改动态库的代码,但重定位信息没有变化,引用该动态库的模块无需从新编译。当然,重定位信息发生改变,两者都需要从新编译。