type
status
date
slug
summary
tags
icon
password
一、编译的分类
编译可以分为以下几种形式:
- 静态编译(static compilation):编译器在编译可执行文件时,把需要用到的对应动态链接库(.so或.ilb)中的部分提取出来,链接到可执行文件中去,使可执行文件在运行时不需要依赖于动态链接库。、
- 动态编译(dynamic compilation):动态编译的可执行文件需要附带一个的动态链接库,在执行时,需要调用其对应动态链接库中的命令。所以其优点一 方面是缩小了执行文件本身的体积,另一方面是加快了编译速度,节省了系统资源。缺点一是哪怕是很简单的程序,只用到了链接库中的一两条命令,也需要附带一个相对庞大的链接库;二是如果其他计算机上没有安装对应的运行库,则用动态编译的可执行文件就不能运行。JIT编译(just-in-time compilation)、自适应编译(adaptive dynamic compilation)都算在静态编译里面。
二、一个经典的编译器结构
一个经典的编译器结构是:前端+中端(优化器)+后端
前端(front end):
与源语言有关。主要的功能是:预编译、词法分析、语法分析、语义分析。
1、预编译:预编译主要是处理源代码文件里面以#开头的预编译语句。主要包含:
- 文件包含:源代码文件里面#include文件包含的声明。
- 宏展开:#define定义的宏语句
- 条件编译(不是条件语句):预处理器根据#if、#ifdef等条件编译指令,将源代码中的某部分代码包含进来或者排除在外,通常将排除在外的语句转化成空行。
(形如:#if 条件 1 代码段 1#elif 条件 2 代码段 2...)
- 删除注释:删除代码里面的所有注释
2、词法分析(字符流->词法分析器->词法单元流):字符流从左到右将字符逐个读入词法分析器里面,识别出各个单词,确定单词的类型。识别出的单词转化成统一的机内表示—词法单元(token)形式,token的形式为<种别码,属性值>。

例如:C程序中的printf(“Hello world!”);
字符流是:p、r、i、n、t、f、(、“、H、e、l、l、o、 、w、o、r、l、d、!、”、)、;
词法分析器分析之后,理解成:printf、(、“、Hello world!、”、)6个单词。
3、语法分析(词法单元流->语法分析器->语法树):将词法分析生成的单词组合成语法短语,同时分析这些短语是否符合高级程序语言设计语言中的语法规则。实现语法分析的一个简单思路就是模板匹配。
(插个题外话:如何进行模板匹配?在github上有这样一个项目:syntax-parse,是用Java做了一个模板匹配的框架,里面有一个匹配规则很有意思:
String dialog = "hi,兄弟。你好,我要买两张从上海前往北京的机票,非常感谢!我要订一张从北京到上海的高铁票啊";
String template = "[W:0-5]" +
"[[我要|帮我][订|买]]" +
"[@sys_num]" +
"从" +
"[#出发地:[@sys_location]]" +
"[出发?]" +
"[到|去|到达|前往]" +
"[#目的地:[@sys_location]]" +
"的" +
"[票|车票|火车票|高铁票|机票|飞机票]" +
"[W:0-5]";
匹配:弟。你好,我要买两张从上海前往北京的机票,非常感谢
变量:{出发地=上海, 目的地=北京}
反正使用的就是语言的一些匹配规则来进行选择。
)
4、语义分析(语法树->语义分析器->语法树):语义分析主要有两个步骤:
① 收集标识符的属性信息:

② 语义检查:
- 变量或者过程未经声明就使用
- 变量或者过程名重复声明
- 运算分量类型不匹配
- 操作符与操作数之间的类型不匹配(例如:数组下标不是整数,函数返回类型有误……)。
中端(optimzer):(中间表现形式->机器无关代码优化器->中间表现形式)
在前端的基础上,编译器将语法树转化成中间代码,再通过一系列的优化对中间代码进行优化,提升代码的性能,将优化后的代码传给后端。
1、 中间代码的生成:
常见的中间形式为三地址码,每个三地址码指令都可以被分解成一个四元组的形式: (运算符,操作数1,操作数2,结果)。


2、 中间代码的优化(这一块在后面会单开一篇)
代码优化阶段的任务是对中间代码进行变换和改造,目的是为了使生成的目标代码更加的高效。常见的代码优化方式有:删除公共表达式、删除无用代码、常量合并、代码移动、强度削弱、删除归纳变量。
后端(back end):(中间表现形式->目标代码生成器->目标机器语言->机器相关代码优化器->目标机器语言)
1、对目标代码的操作:
① 目标代码的生成:是把中间代码变换成特定机器上的目标代码,包括绝对指令代码、可重定位的指令代码、汇编指令代码。还与各种数据类型变量的存储空间分配以及寄存器的合理分配有关。
② 目标代码的优化:这个阶段的优化主要是与目标机器的硬件结构有关,主要考虑的是利用机器的寄存器存放有关变量的值,以此来减少内存的访问次数;此外还包括一些超长指令的优化。目的是使生成的目标代码更简洁高效。
2、链接:
链接的功能是将一个或者多个目标文件及库文件合并成一个可执行文件。链接可以分为静态链接和动态链接。
① 静态链接:链接器将外部函数所在的静态链接库直接拷贝到目标可执行程序中,这样在执行该程序时这些代码就会被装入该进程的虚拟地址空间中。Windows的.lib、Linux的.a文件。
② 动态链接:将程序拆分成相对独立的部分,在程序运行的时候才将他们链接在一起形成一个完整的程序。动态链接文件被称为共享对象,一般是以.so结尾的文件。Windows下的.dll文件、Linux文件下的.so文件
特点 | 类型 | ㅤ |
ㅤ | 静态链接 | 动态链接 |
链接时机 | 形成可执行程序前 | 程序执行时 |
方式 | 地址与空间分配和符号解析与重定位 | 装载时重定位和地址无关代码技术 |
库拓展名 | .a | .so |
优点 | 程序启动快,运行速度快、方便移植 | 节省内存空间和磁盘空间 |
缺点 | 浪费内存和磁盘空间 | 增加程序在运行时的链接开销,可移植性差 |
3、目标文件的生成
现在主流的PC平台的可执行文件格式为主要包含以下两种:
- Windows下的PE文件(Portable Executable)
- Linux下的ELF文件(Executable Linkable Format)

.text节:保存了程序代码指令的代码节。
.rodata节:保存了只读的数据。
.plt节(过程链接表):其包含了动态链接器调用从共享库导入的函数所必需的相关代码。
.data节:其保存了初始化的全局变量等数据。
.bss节:占用空间不超过4字节,仅表示这个节本省的空间。.bss节保存了未进行初始化的全局数据。
.got.plt节(全局偏移表-过程链接表):.got节保存了全局偏移表。.got节和.plt节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改。
.rel.*节(重定位表)
重定位表保存了重定位相关的信息,这些信息描述了如何在链接或运行时,对ELF目标文件的某部分或者进程镜像进行补充或修改。由于重定位表保存了重定位相关的数据,所以节类型为SHT_REL。
参考文章:
- 作者:JucanaYu
- 链接:https://jucanayu.top/article/34e5dc6d-5812-4c55-9723-880ea52ef18b
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。