《程序员的自我修养》第二章 编译和链接

《程序员的自我修养》第二章 编译和链接_第1张图片

正文

第二章主要介绍了编译的具体步骤、汇编以及链接这一步骤的由来与作用。

2.1 被隐藏了的过程

计算机的 IDE 将编译和链接合并成一步,称为构建(Build)。分别介绍了构建的四个步骤:预处理编译汇编链接

  • 预处理:主要处理代码中,以“#”开始的预编译指令,如“#include”,“#define”等。展开宏定义,处理条件预编译指令,删除注释,保留 #pragma(因为编译器需要使用),递归引入文件(#include)。
  • 编译:将预处理完的文件,经过词法分析、语法分析、语义分析、代码优化后产出汇编代码。
  • 汇编:将汇编代码转换成机器可以执行的指令,输出目标文件。这个过程相对比较简单。因为汇编代码和机器指令是一一对应,翻译即可。
  • 链接:将多个目标文件链接起来,输出可执行文件。

2.2 编译器做了什么

编译器的作用包括:

  • 让开发者不用依赖于某个特定平台,使用机器指令或者汇编代码从事开发工作,而是使用更高级更通用的语言进行开发。
  • 高级语言让开发者更加关注程序的逻辑本身,而不用考虑计算机限制,开发效率和可移植性更高。

编译器的各个步骤介绍:


《程序员的自我修养》第二章 编译和链接_第2张图片

比如我们有一行 C 语言代码如下:

array[index] = (index + 4) * (2 + 6)
  • 词法分析:将源代码输入到扫描器当中,根据一种有限状态机的算法,将代码分割成一系列的记号(Token)。记号可分为:关键字、标识符、字面量(数字、字符串)、特殊符号(如加号、等号)。
    《程序员的自我修养》第二章 编译和链接_第3张图片
  • 语法分析语法分析器扫描器输出的一系列记号,处理生成以表达式为节点的语法抽象树。
    《程序员的自我修养》第二章 编译和链接_第4张图片
  • 语义分析语义分析器根据静态语义,将整个语法抽象树上的表达式都赋予类型。如类型和声明的匹配、类型的隐式转换,都在这一步完成。
    《程序员的自我修养》第二章 编译和链接_第5张图片
  • 中间语言生成:将语法抽象树,转化为中间代码。代码会有多个层次的优化,源码级优化器负责在源码上实现优化,比如将2 + 6 直接简化为确定的8。常用的中间代码有三地址码,其形式为 z = x op y。
    《程序员的自我修养》第二章 编译和链接_第6张图片

到这一步为止,属于编译器前端干的事情。编译器前端负责产生与机器无关的中间代码。而编译器后端将中间代码转换成目标机器代码。

  • 目标代码生成与优化:包括了代码生成器目标代码优化器代码生成器负责将中间代码生成特定的由目标机器类型决定的目标代码。目标代码优化器负责在目标代码层面进行优化,包括了:选择合适的寻址方式,使用位移来代替乘法运算、删除多余指令等。

疑问:index 和 array 的地址还没有确定。如果要把目标代码通过汇编器转换为在机器上可以执行的指令,我们从哪里获得 index 和 array 的地址呢?如果 index 和 array 定义在跟上面源代码同一个编译单元时,可以由编译器为 index 和 array 分配空间。可如果没有在同一个编译单元时呢?

2.3 链接器年龄比编译器长

机器语言时代

《程序员的自我修养》第二章 编译和链接_第7张图片

假设有一种计算机,它的每条指令为1个字节,也就是8位。假设有一种跳转指令,高4位是0001,表示这是一条跳转指令,低4位存放的是跳转目的地的绝对地址。如上图,可以看出程序中的第1条指令会跳转到第5条指令。问题在于,程序不是不是一成不变的,如果在第5条指令之前,新插入一条语句,程序员就需要重新计算所有在新插入语句后的语句位置。这个过程称为 重定位。对于程序员来说,这个过程实在太过于繁琐,必须想一个办法解决。

汇编语言:引入符号的概念,利用符号来指代指令和地址。避免人工的重定位工作,由计算机来搞定。“jmp”指代跳转命令,“foo”指代第五条指令。那么就可以把第一条指令修改为非常简洁易懂的

jmp foo

随着代码规模的增加,代码模块化就显得非常有必要了。但是划分完以后,各个模块之间如何通信又成了一个问题。
链接:模块间的符号引用可以解决这个问题。类似于模块的拼接。

2.4 模块拼装——静态链接

链接的主要工作:把各个模块相互引用的部分都处理好,使得每个模块能够正确地衔接。
链接过程包括:地址和空间分配、符号决议、重定位。

《程序员的自我修养》第二章 编译和链接_第8张图片

如上图,每个模块的源代码文件经过编译器和汇编器生成目标文件,目标文件和库链接在一起,形成可执行文件。最常见的库是 运行时库,它是支持程序运行的基本函数的集合。

最后讲讲是如何修正这些在其他模块的变量和函数的地址的?
比如我们在程序模块 main.c 引用了另一个模块 fun.c 中的函数 foo()。在单独编译过程中,main.c 模块无法确切知道 foo() 指令的目标地址,先暂行搁置。等到最后的链接过程中,链接器负责寻找foo() 的目标地址,并进行修正。

结束

你可能感兴趣的:(《程序员的自我修养》第二章 编译和链接)