1. 编译器

1.1 什么是编译器

语言处理器:语言翻译程序,将源语言程序转变为用目标语言书写的语义等价程序

编译器:将高级语言编写的源代码转变为汇编语言编写的中间代码

为什么是汇编语言而不是机器语言

  • 人类可读,方便检查调试
  • 作为中间层可以适配不同硬件
  • 模块化流程,降低编译器设计的复杂度

常见的 C 语言编译器

  • GCC:用于编译 C、C++、Fortran 等多种语言,生成汇编代码/机器码,由 GNU 汇编器和链接器进一步处理
  • Clang:基于 LLVM 的前端编译器,支持 C、C++、Objective-C 等语言,以优美的错误信息和高效的优化著称,生成 LLVM 中间表示再生成机器码
  • MSVC:微软提供的编译器,主要用于 Windows 平台上的 C 和 C++ 程序开发,与 Visual Studio 紧密集成,生成高效的目标代码

1.2 高级语言程序转换过程

以 C 语言为例

  1. 预处理器:对源代码中的预处理指令展开,如宏替换、条件编译等
  2. 编译器:将源代码变为汇编代码,进行词法分析、语法分析、语义分析和代码优化与生成
  3. 汇编器:将汇编代码变为机器代码
  4. 链接器:将机器代码变为可执行文件,将多个目标文件和库文件合并,进行符号解析、地址重定位、消除外部符号引用等工作
  5. 加载器:将可执行文件加载到内存作为可执行程序,进行内存分配、地址重定位、动态链接等工作

1.3 解释器

特性编译器(Compiler)解释器(Interpreter)
定义将整个源代码一次性翻译成目标代码,然后生成独立的可执行文件逐行或逐块读取并即时翻译执行源代码,不生成独立的可执行文件
翻译时机先翻译再执行翻译和执行同步进行
执行效率生成的目标代码直接运行,通常具有较高的执行效率实时翻译会带来额外的时间和空间开销,效率低
错误检测在编译阶段一次性检查整个程序,发现并报告多数语法和类型错误错误通常在执行到出错语句时才被检测到,有助于调试但可能延后错误暴露
跨平台性编译结果通常针对特定平台,需要为不同平台重新编译只要目标平台有相应解释器环境,同一源代码可直接运行

1.4 编译类型

C/C++:静态编译,先将源代码转换成机器码(.o),然后链接生成独立的可执行文件

Python:解释,先被编译成平台无关的字节码(.pyc),然后由解释器逐行解释执行

Java:即时编译,先将源代码编译成平台无关的字节码(.class),最初由解释器逐行解释执行,但是运行过程中即时编译器会将被频繁执行的“热点字节码”动态编译成机器码,从而大幅提高性能

2. 编译过程

2.1 前端:分析

  1. 词法分析(lexical):识别源代码字符流中的单词、关键字、数字、运算符、标识符、界符等词素(lexeme),忽略空格、换行符、注释等不影响程序逻辑的内容,将识别出的词素单元转换成记号(token)

  1. 语法分析(syntax):使用文法规则解析记号序列,将记号流分解为各类语法单元,如程序、语句、表达式等,检查源程序在语法上是否正确,构建出语法树

  1. 语义分析(semantic):审查源程序是否有语义错误,进行类型检查、作用域检查、参数匹配检查等,为代码生成阶段收集信息

2.2 后端:综合

  1. 中间代码生成(intermediate code):遍历上述语法树,生成等价的近似“三地址指令”的四元式中间代码,是一种结构简单、含义明确的记号系统,具有容易生成、易于转换的特点,表示为(运算符, 运算对象1, 运算对象2, 结果)

  2. 中间代码优化(optimization):改进程序结构,减少冗余,从而节省时间和空间

  3. 目标代码生成(generation):把优化后的中间代码转化为目标代码,通常需要进行指令选择、寄存器分配、内存计算和调度等工作

同一源语言的前端加上不同的后端可以为不同机器提供同一源语言的编译,不同源语言的前端加上相同的后端可以为同一机器提供不同源语言的编译

2.3 辅助程序

表格管理程序:管理和操作各种编译器中使用的数据表,如符号表、关键字表、操作码表等,为语义分析、代码生成等阶段提供必要的信息支持,

出错处理程序:负责检测、记录和报告编译过程中的各种错误,如语法错误、语义错误等,并在可能的情况下尝试进行错误恢复,以继续处理后续代码

3. 符号表

符号表(symbol table):记录标识符(变量和函数)以及其属性(名称、类型、作用域、内存地址、初始值)等信息的数据结构,可以用于进行作用域检查、类型检查、内存分配、寄存器分配等工作

  • 管理不同作用域下标识符的可见性和生命周期,处理嵌套作用域、全局与局部变量的冲突问题
  • 利用符号表进行类型检查、重定义检查、引用检查等,确保程序中标识符的使用符合语言的语义规则
  • 确定变量在内存中的布局、寄存器分配等,从而支持生成正确且高效的目标代码

4. 软件工具

  1. 结构化编辑器:自动维护代码的语法和结构完整性,避免低级语法错误,通过树状视图或语法高亮,帮助开发者直观理解代码结构
  2. 调试工具:提供单步执行、断点设置、变量观察和内存检查等功能,帮助开发者跟踪程序执行流程、定位逻辑或运行时错误
  3. 程序格式化工具:自动调整缩进、换行、空格等,统一代码风格,提升代码可读性和维护性
  4. 程序测试工具:静态分析器在不运行程序的情况下,检查是否存在已经定义过的常见错误,动态分析器在程序的适当位置加入测试语句,以在运行时显示测试结果帮助开发者定位问题
  5. 程序理解工具:通过静态代码分析、依赖关系图、代码度量等手段,帮助开发者快速掌握复杂系统
  6. 转译器:将一种高级程序设计语言的源代码转换为另一种高级程序设计语言的源代码,用于匹配底层环境、实现代码迁移和扩展语言功能

许多编译器会使用转译器将其他高级语言的源代码变为 C 语言后再进行编译,这是因为 C 语言已经拥有完善和成熟的编译器和优化器,且 C 语言具有良好的跨平台特性,同时在语法上更接近汇编语言,有助于进行高效的目标代码生成和优化

5. 语言范式

  • 命令式:描述计算过程,通过一系列明确的语句和指令来改变程序状态(C)
  • 函数式:将计算视为数学函数的求值,强调不可变性和无副作用,常用递归和高阶函数来构建程序(Haskell)
  • 声明式:关注 “做什么” 而不是 “如何做”,让系统自动决定求解步骤(SQL)
  • 面向对象:以对象和类为核心组织程序,强调封装、继承和多态(Java)
  • 脚本:具有高层次运算符的解释语言,将多个计算过程粘合在一起,通常用于自动化、快速开发和系统管理(Python)