编译和连接
1. 预处理(Prepressing)
- 将所有的
#define删除,并展开宏定义。 - 处理所有的条件预编译,比如
#if#ifdef#elif#else#endif。 - 处理
#include预编译指令,将包含的文件插入到该预编译指令的位置,注意,这个过程是递归。 - 删除所有的
//和/* */ - 添加行号和文件名标识
- 保留所有的
#pragma编译指令
2. 编译(Compilation)
编译就是把预处理完的文件进行一系列的词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件。
gcc 这个命令只是后台程序的包装,它会根据不同的参数要求去调用预编译编译程序cc1、汇编器as、链接器来ld。
3. 汇编(Assembly)
将汇编代码转变成机器可以执行的指令
4. 链接(Linking)
- 词法分析
首先源代码程序被输入到扫描器(scanner),它运用一种类似有限状态机(Finite State Machine)的算法将源代码的字符序列分割成一系列的记号(Token) - 语法分析
语法分析器(Grammar Parser)将对由扫描器产生的记号进行语法分析,产生语法树(Syntax Tree),整个过程采用长下文无关语法(Context-free Grammar)的分析手段。语法树就是以表达式(Expression)为及节点的树。 - 语义分析
由语义分析器(Semantic Analyer)来完成。所能分析的语义是静态语义(Static Semantic),通常包括声明和类型的匹配、类型的转换。 - 中间语言生成
源码级优化器(Source Code Optimizer)将整个语法树转换成中间代码(Intermediate Code) - 目标代码的生成与优化
源代码级优化器产生中间代码标志着下面的过程都属于编辑器后端,主要包括的代码生成器(Code Generator)和目标优化器(Target Code Optimizer)。代码生成器将中间代码转换目标机器码。 - 链接
链接的主要过程包括:地址和空间分配(Adress and Storage Allocation)、符号决议(symbol resolution)和重定向(Relocation)等步骤。
目标文件是什么样的
目标文件的内容至少有编译后的机器指令代码,数据。还有链接时所需要的信息,如符号表,调试信息,字符串等。
- 程序源代码编译后的机器指令经常被放在
代码段(Code Setion),常见名字有“.code”和 “.text” - 全局变量和局部静态变量数据经常放在
数据段(Data Section),一般名字叫“.data” -
.bss段(Block Started by Symbol)只是为未初始化的全局变量和局部静态变量预留位置而已,并没有内容,它在文件中不占据空间。
自定义段
GCC提供了一个扩展机制,使得程序员可以指定变量所处的段:
_attribute_((section("FOO"))) int global = 42;
_attribute_((section("Bar"))) void foo ( )
{
}
我们在全局变量或者函数之前加上“_attribute_((section("name")))”属性就可以把相应的变量或者函数放到以“name”作为段名的段中。
ELF文件结构描述
ELF文件结构图
| ELF Header |
|---|
| .text |
| .data |
| .bss |
| other sections... |
| Section header table |
| String Tables |
| Symbol Tables |
| ... |
静态链接
1.空间与地址分配
主要有两种方式:按序叠加和相似段合并。按序叠加会造成内存空间大量的内部碎片。所以一个更实际的方法是将相同性质的段合并在一起,叫做相似段合并。比如将所有输入文件的“.text”段合并到输出文件的“.text”段。使用这相思段合并方法的链接器一般都采用一种叫两步链接(Two-pass Linking)的方法,也就说整个过程分两步。
- 空间与地址分配
- 符号解析与重定位