OS - Compile and Link
Created by : Mr Dk.
2020 / 06 / 25 16:42
Nanjing, Jiangsu, China
内容来自于 程序员的自我修养 - 链接、装载与库,俞甲子 石凡 潘爱民著。
Whole Procedure
IDE 将编译、链接的过程一步完成。这种编译和链接合并到一起的过程称为 构建 (Build)。而实际上,被 IDE 隐藏的过程是非常多的:
预编译
预编译由 预编译器 (cpp) 完成,将 C/C++ 源代码预编译为 .i
/ .ii
文件。这一过程主要处理源代码中以 #
为开头的预编译指令:
- 展开
#define
宏定义 - 处理条件编译指令
#ifdef
#if
等 - 处理
#include
递归地将将被包含的文件插入到该指令的位置 - 删除所有注释
- 添加行号和文件名标识 (方便编译器产生警告或错误提示)
- 保留
#progma
编译器指令 (编译器需要使用)
编译
编译器完成词法分析、语法分析、语义分析、优化,产生汇编代码文件 .s
。GCC 命令使用 cc1
程序完成预编译和编译两个步骤。此外,gcc
命令只是后台各个部件的封装,会根据不同的参数去调用不同的后台部件:
cc1
- 预编译、编译程序as
- 汇编器ld
- 链接器
汇编
将汇编代码转换为机器可以执行的指令。基本上每一个汇编语句都对应一条机器指令,所以这个过程实际上就是一个查表翻译的过程,比较简单。由此,生成了目标文件 .o
。
链接
需要将一大堆目标文件链接在一起,才能生成可执行文件。
Compiler
编译器所做的工作只是上述完整流程中的一部分。编译本身分为六步:
- 词法分析 - 通过状态机将字符序列切分为 token,将标识符、数字常量、字符串常量等放到对应的表中
- 语法分析 - 产生 AST
- 语义分析 - 分析静态语义:声明和类型的匹配、类型转换
- 源代码优化 - 将代码转换为中间代码 (IR) 并进行优化
- 代码生成 - 依赖目标机器
- 目标代码优化 - 合适的寻址方式等
在生成的目标代码中,有一些引用外部的绝对地址没有被确定。如果目标代码想要成为真正能被运行的指令,这些地址应该从哪里获得?
Static Linking
汇编语言的出现解决了两个问题:
- 用指令助记符替代了指令对应的二进制代码
- 用标号替代了会因插入或删除行而反复变化的绝对地址
由于程序设计的模块化,人们把每个源代码模块独立地编译,再按要求组装起来。这个组装模块的过程就是链接。主要工作是把一些指令对其它 符号地址 的引用加以修正。链接过程包括:
- 地址、空间分配 (Address and Storage Allocation)
- 符号决议 (Symbol Resolution)
- 重定位 (Relocation)
目标文件与库文件一起链接,称为可执行文件。
由于每个模块都是单独编译的,假设源文件中引用了一个库函数中的文件,而在编译时并不知道这个库函数的地址。那么就暂时把这个函数的目标地址搁置,等链接是由链接器负责将这个地址修正。
使用链接器,我们就可以直接引用其它模块的函数和全局变量,不用管它们的具体地址。由链接器根据引用的符号去相应模块查找真正的地址,再修正当前模块中的地址 - 这一过程被称为 重定位,每个需要被修正的地方被称为重定位入口。以上是静态链接的基本过程和作用。