编译和连接
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)
的方法,也就说整个过程分两步。
- 空间与地址分配
- 符号解析与重定位
网友评论