背景

在编译 C 或者 C++ 文件的时候,我们都会用到链接这一步骤,即 预处理 - 编译 - 汇编 - 链接 四个步骤
我们已经学过了汇编了,那么接下来就该了解一下什么是 linking 了

什么是链接

在汇编中,我们一般是每一个 c/cpp 文件生成一个 汇编代码文件 (obj file, 扩展名 .o),那么,如果存在跨文件的变量,汇编怎么知道这个变量是来自哪里的呢?
首先我们联想在编译的时候 (compile time) 我们会在项目编译的内存空间中创建一个格式为 text - data - heap - stack 的空间结构(这里说的不恰当,应该说 compile time 分配的 text - data 空间),那么类似的,对于每个源文件生成的汇编码,我们都要用一个表来记录全局变量的引用 crossrefcross-ref 以及外来方法 (即函数) 的对应引用

obj 文件的内容

上述两个表格会在编译的过程中存储在 .o 文件中,因此 obj 文件的格式会表现为:
Header - Text - Data - Symbol Table - Relocation Table - debug info(这里不细讲)

  • header 部分存储了每个部分的size,从而能较快的定位各个部分
  • text 部分存储了各个指令的 汇编格式
  • data 部分会存储全局变量以及本地的 static 变量
  • symbol table 会存储本地全局变量以及外来全局变量以及函数,并且标记本地变量和函数的地址
    • 这个表全局可见
    • 会记录变量/函数的位置, 变量位于 data 区,函数位于 text 区域
  • relocation table 会存储外来陌生函数/变量的行号,指令对应的 汇编码 的操作码以及以来的变量名
    • 外来变量 / 函数只有在被使用(调用或者赋值)的时候才会进入 relocation table, 因此存储的行号是 调用行号 而不是 引入行号

Linker 的任务

一般而言,linker 会

  1. 首先将所有文件的 text 部分连接起来放到上述编译空间的 text 部分
  2. 然后再将 所有文件的 data 部分连接起来放到上述编译空间的 data 部分
  3. 将跨文件的引用通过 symbol table 进行符号解析,统一各个文件的符号指代内容
    1. 这一步的目的是将总汇编文件的变量名统一,因为单文件编译的时候的外来变量/函数在汇编的时候可能是虚位以待的,这里完成了识别解析引用
    2. 但是到此为止,各个文件内的函数和变量的地址指代都是不互通的,我们只知道符号是对应的,其对应的地址并没有据此更新
  4. 在这之后开始重定位,将所有的 relocation table 的值替换成新的合成文件的绝对地址
    1. 有一些对于 PC 进行相对位移的操作是不会进行重定位的
      注:可执行文件内部并不会包含这两张表

什么是 lib?

我们将集成的特殊 obj 文件集称为 libraries
现在你已经学会了静态链接库的原理了,那么,请编写一个 #<iostream> 链接库吧

加载器 Loader

在完成了链接之后,操作系统如何执行这个程序呢?首先会将这个文件的代码放到内存中,并且要求系统给其分配一个进程来完成
系统的进程首先需要给其分配一个足够大的内存来容纳 text 和 data 部分, 并且会预分配一定内存给 stack 空间
之后,将 data 和 指令集部分移动到新的内存空间进行运行使用
并且会初始化 PC 以及 SP (栈顶指针寄存器)