我们编写的程序从源码到可执行文件一般经过如下过程:
- 预处理
- 编译
- 汇编
- 链接
首先,我们以一个.c文件为例看一下每一个阶段都做了哪些事情。
预处理阶段(Preproceessing)
gcc -E aaa.c -o aaa.i
源码文件aaa.c
经过预处理生成一个aaa.i文件。在这个阶段所做的工作是:
- 将所有的#define删除,并且展开所有的宏定义
- 处理程序中所有的条件预编译指令, 如
#if #ifdef #elif #else #endif
- 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置
- 删除所有的注释
//
和/**/
- 添加行号和文件标识,以便编译时产生调试用的行号以及编译错误警告行号
- 保留所有的#pragma编译器指令,因为编译器需要使用它们。
可以查看预处理后的aaa.i
文件内容:
编译(Compilation)
编译过程就是把预处理完的文件进行一系列的词法分析,语法分析,语义分析以及优化后生成相应的汇编代码。
gcc -S aaa.i -o aaa.s
将预处理后的.i文件经过编译生成.s文件。
汇编(Assembly)
汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句都对应一条机器指令。汇编就是根据汇编指令和机器指令的对照表进行一一翻译的过程。
gcc -c aaa.s -o aaa.o
将编译后生成的.s文件通过汇编器转为.o文件。
链接
通过调用链接器Id来链接程序运行需要的一大堆目标文件,以及它所依赖的其它库文件,最后生成可执行文件。
gcc aaa.o -o aaa
将.o文件转换为可执行文件。
编译器和链接器在这些过程中都做了些什么呢?
编译过程
词法分析:扫描器(Scanner)将源代码中的字符序列分割成一系列的记号(Token)。lex工具可以实现词法扫描。
语法分析:语法分析器将记号(Token)产生语法树(Syntax Tree),yacc工具可以实现语法分析
语义分析:静态语义、动态语义
源代码优化:源代码优化器,将整个语法树转化为中间代码。中间代码使得整个编译器被分为前端和后端,编译器前端负责产生机器无关的中间代码;编译器后端将中间代码转化为目标机器代码。
链接过程
链接的主要内容是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确地衔接。
链接的主要过程包括:地址和空间分配,符号决议,重定位等。
链接又分为静态链接和动态链接。
静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。
动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再去从系统中把相应动态库加载到内存中去。
静态链接与动态链接主要的区别:
第一,考虑内存和磁盘空间。静态链接极大地浪费内存空间。因为在静态链接的情况下,假设有两个程序共享一个模块,那么在静态链接后输出的两个可执行文件中各有一个共享模块的副本。如果同时运行这两个可执行文件,那么这个共享模块将在磁盘和内存中都有两个副本,对磁盘和内存造成极大地浪费;第二,程序的更新。一旦程序中的一个模块被修改,那么整个程序都要重新链接、发布给用户。如果这个程序相当的大,那么后果就会更加严重!
而动态链接是这样的:
动态链接是相对于共享对象而言的。动态链接器将程序所需要的所有共享库装载到进程的地址空间,并且将程序汇总所有为决议的符号绑定到相应的动态链接库(共享库)中,并进行重定位工作。