编译器Compiler)是把源程序转换成另一种程序表示的工具,常见目标是中间表示(IR)、汇编、目标文件或可执行代码;转换过程中它还负责语法/类型诊断、优化和目标平台代码生成。

核心问题

人写的源码适合表达意图,机器执行需要指令、寄存器、内存布局和调用约定。编译器解决的是“如何把高级语言语义可靠地映射到目标平台执行模型”。

典型流水线:

source code
  -> lexical/syntax analysis
  -> semantic analysis
  -> intermediate representation
  -> optimization
  -> code generation
  -> object file / executable

不同语言和工具链会拆分这些阶段。例如 Clang 负责 C/C++/Objective-C 前端,LLVM 优化器和后端负责 IR 优化与机器码生成;GCC 则在自己的前端、中端和后端框架内完成类似流程。

核心对象

对象作用
frontend解析源码,做语义检查,把语言结构转换成 AST 或 IR。
AST抽象语法树,保留接近源码结构的语法和语义信息。
中间表示(IR)适合优化和跨平台代码生成的中间层。
optimizer对 IR 做等价变换,提升速度、减小体积或降低资源消耗。
backend把 IR 降低到目标平台指令、寄存器和ABI 规则。
drivergccclangcl.exe 这类入口程序,调度预处理器、编译器、汇编器和链接器

核心机制

编译器的每一步都在维护一个不变式:转换后的程序要保留源语言规定的可观察行为。

source_semantics(program) == target_semantics(compile(program))

这里的等号不是说源码和机器码长得一样,而是说在语言标准、目标平台和 ABI 约束下,程序的外部可观察结果一致。优化器能删代码、改顺序、内联函数,是因为它证明这些变换不改变可观察行为;如果源码依赖未定义行为,证明前提就可能不成立,优化结果会显得“反直觉”。

常见优化包括:

  • 局部优化:常量折叠、公共子表达式消除、死代码删除。
  • 循环优化:循环不变代码外提、展开、向量化。
  • 过程间优化:跨函数内联、函数克隆、去虚化。
  • LTO(链接时优化):把一部分跨翻译单元优化推迟到最终链接阶段。

工程用途

理解编译器有助于排查这些问题:

  • 为什么 Debug 和 Release 行为不同。
  • 为什么某个函数被内联后断点或栈帧消失。
  • 为什么不同编译器或版本会造成 ABI 不兼容。
  • 为什么开启 LTO(链接时优化) 后链接变慢但运行时变快或体积变小。
  • 为什么 sanitizer、静态分析和警告能在构建期发现问题。

边界与常见坑

  • 编译器不是构建系统。CMake、Bazel、Make 负责组织构建图;编译器负责单个编译/链接动作中的语言转换。
  • 编译器驱动不等于纯编译器。gcc main.c -o app 会调用编译、汇编和链接多个阶段。
  • 编译错误、链接错误、运行时错误属于不同阶段。把 undefined reference 当成语法错误处理,会走错排查路径。
  • 优化不保证性能一定更好。需要用性能剖析和 benchmark 验证,而不是只看优化级别。
  • 语言标准、目标 CPU、操作系统 ABI、运行时库和链接器共同决定最终二进制行为。

相关术语