小书童
发布时间

编译系统结构

作者

编译系统结构

编译系统是计算机科学中的重要组成部分,它将高级编程语言转换为机器可执行的代码。一个完整的编译系统通常包含以下几个主要阶段:

1. 词法分析(Lexical Analysis)

从左向右逐行扫描源程序的字符,识别出各个单词,确定单词类型,将识别的单词转换成统一的机内表示(词法单元token)

token<种别码,属性>

序号单词类型种别种别码
1关键字if、else、then...一词一码
2标识符变量名、数组名多词一码
3常量整数、浮点数一型一码
4运算符+、-一词一码或一型一码
5界限符;{ ...一词一码

词法分析器的主要功能:

  • 识别并分类各种词法单元
  • 过滤空白字符和注释
  • 处理字符串常量和数字常量
  • 生成token流供语法分析器使用

示例:

int main() {
    int x = 10;
    return x;
}

词法分析后生成的token序列:

<KEYWORD, int>
<ID, main>
<LPAREN, (>
<RPAREN, )>
<LBRACE, {>
<KEYWORD, int>
<ID, x>
<ASSIGN, =>
<NUMBER, 10>
<SEMICOLON, ;>
<KEYWORD, return>
<ID, x>
<SEMICOLON, ;>
<RBRACE, }>

2. 语法分析(Syntax Analysis)

从词法分析器中输出的token序列中识别出各类短语,并构造语法分析树(parse tree)

语法分析树描述了句子的语法结构

语法分析的主要任务:

  • 根据语言的语法规则分析token序列
  • 检测语法错误
  • 构建抽象语法树(AST)
  • 为语义分析提供结构化的程序表示

常见的语法分析方法:

  • 递归下降分析
  • LL(1)分析
  • LR分析
  • LALR分析

示例语法树:

Program
├── FunctionDeclaration
│   ├── Type: int
│   ├── Name: main
│   └── Body
│       ├── VariableDeclaration
│       │   ├── Type: int
│       │   ├── Name: x
│       │   └── Value: 10
│       └── ReturnStatement
│           └── Expression: x

3. 语义分析(Semantic Analysis)

3.1 收集标识符的属性信息

  • 种属(kind):变量、函数、类等
  • 类型(type):int、float、string等
  • 存储位置、长度:内存分配信息
  • :常量值或初始值
  • 作用域:变量的可见范围
  • 参数和返回值信息:参数个数、参数类型、参数传递方式、返回值类型等

3.2 语义检查

  • 变量或过程未经声明就使用
  • 变量或过程名重复声明
  • 运算分量类型不匹配
  • 操作符与操作数之间的类型不匹配
  • 数组下标不是整数
  • 对非数组变量使用数组访问操作符
  • 对非过程名使用过程调用操作符
  • 过程调用的参数类型或数目不匹配
  • 函数返回类型有误

符号表管理: 符号表是语义分析的核心数据结构,用于存储标识符的各种属性信息。

4. 中间代码生成

中间代码是介于源程序和目标代码之间的一种表示形式,具有以下特点:

  • 与具体机器无关
  • 便于优化
  • 易于转换为目标代码

4.1 三地址码(Three-address Code)

三地址码由类似于汇编语言的指令序列组成,每个指令最多有三个操作数(operand)

常见的三地址指令

序号指令类型指令形式四元式形式
1赋值指令x=y op z;x = op y(op,y,z,x);(op,y,_,x)
2复制指令x=y(=,y,_,x)
3条件跳转if x relop y goto n(relop,x,y,n)
4非条件跳转goto n(goto,_,_,n)
5参数传递param x(param,_,_,x)
6过程调用call p,n(call,p,n,_)
7过程返回return x(return,_,_,x)
8数组引用x = y[i](=[],y,i,x)
9数组赋值x[i]=y([]=,y,x,i)
10地址及指针操作x= &y;x =*y;*x =y(&,y,_,x); (=* ,y,_,x); ( *= ,y,_,x)

4.2 地址形式

地址可以具有如下形式之一:

  • 源程序中的名字
  • 常量(constant)
  • 编译器生成的临时变量(temporary)

4.3 中间代码表示方法

  • 四元式(quadruples):(op,y,z,x)
  • 三元式(Triples):更紧凑的表示
  • 间接三元式(Indirect triples):便于优化的表示
  • 语法结构树/语法树(Syntax Trees):树形结构表示

示例:将表达式 a + b * c 转换为三地址码

t1 = b * c
t2 = a + t1

5. 代码优化

代码优化是编译过程中的重要环节,主要目标包括:

  • 局部优化:基本块内的优化
  • 全局优化:跨基本块的优化
  • 循环优化:循环结构的特殊优化
  • 寄存器分配:合理分配寄存器资源

常见优化技术:

  • 常量折叠
  • 死代码消除
  • 循环展开
  • 函数内联
  • 强度削弱

示例优化:

// 优化前
int x = 2 + 3;
int y = x * 4;

// 优化后
int x = 5;  // 常量折叠
int y = 20; // 常量传播

6. 目标代码生成

目标代码生成以源程序的中间形式作为输入,并把它映射到目标语言

主要任务:

  • 指令选择:选择合适的机器指令
  • 寄存器分配:为变量分配寄存器
  • 指令调度:优化指令执行顺序
  • 代码布局:优化代码在内存中的布局

目标代码生成的重要考虑因素:

  • 目标机器的指令集架构
  • 内存模型和寻址方式
  • 调用约定和ABI
  • 性能优化需求

示例:x86汇编代码

main:
    push    rbp
    mov     rbp, rsp
    mov     DWORD PTR [rbp-4], 10
    mov     eax, DWORD PTR [rbp-4]
    pop     rbp
    ret

7. 编译系统的整体流程

源程序 → 词法分析 → 语法分析 → 语义分析 → 中间代码生成 → 代码优化 → 目标代码生成 → 可执行程序

每个阶段都有明确的输入输出,前一阶段的输出作为后一阶段的输入,形成完整的编译流水线。

8. 现代编译器的发展趋势

  • 多阶段优化:在编译的不同阶段进行优化
  • 即时编译(JIT):运行时编译优化
  • 并行编译:利用多核处理器加速编译过程
  • 增量编译:只重新编译修改的部分
  • 跨平台编译:支持多种目标平台

9. 实际应用中的编译器

主流编译器:

  • GCC:GNU编译器套件,支持多种语言
  • Clang/LLVM:模块化编译器基础设施
  • MSVC:Microsoft Visual C++编译器
  • Javac:Java编译器
  • TypeScript编译器:将TypeScript编译为JavaScript

编译器的应用领域:

  • 系统软件开发
  • 应用程序开发
  • 嵌入式系统
  • 高性能计算
  • 移动应用开发

编译系统是计算机科学的基础,理解其结构和工作原理对于深入理解编程语言和系统软件具有重要意义。通过掌握编译原理,开发者可以更好地理解代码的执行过程,编写更高效的代码,并能够开发自己的编程语言或领域特定语言(DSL)。