带你速通编译原理

11 阅读8分钟

我,子牙老师,一个手写过操作系统、编程语言的硬核男人

让我用一篇文章带你速通编译原理

编译原理是什么?编译原理是所有编程语言得以诞生,背后的理论依据

任何编程语言,从运行,到看到结果,都要经历这些阶段

理解了这张图中的每个名词、每个阶段,你就能看懂任何编程语言的底层实现

词法分析

先从词法分析开始。它的输入是程序代码,输出是记号流(token)

如果你想查看Python代码经词法分析后生成的token

对应代码

image.png

其他编程语言你如果想查看,自行面向AI研究

词法分析器

在所有编程语言中,负责词法分析的模块叫词法分析器。

如果你想得到词法分析器,有两种方式:自实行、使用词法分析工具生成

先说词法分析工具,开源的还蛮多的

我们这套课程使用的是flex,它的工作流程差不多是这样

细节在后面的课程中再细讲,当前你理解到这个程度即可

绝知此事要躬行

诚然,你可以使用开源的词法分析工具flex生成词法分析器,在此基础上开发编程语言

但是在用的过程中,脑海中一直会有一个声音:它是如何实现的

而且遇上问题,无从下手,因为你不知道它的底层实现

因为理解得不深,还会出现一个问题就是:有时候想实现一个功能但是不知道怎么用它实现

为此,写一个玩玩吧(我觉得任何底层,都值得写一遍,而且只需要写一遍)

很多人学习是为了学习而学习

比如学《编译原理》,直接去啃书。一点概念都没有,怎么看得懂呢

我喜欢先动手干,把书当成工具,遇到问题不知道如何解决,在书中找答案

就写出来了,写出来再后头看理论,就很容易理解

比如词法分析中最难理解的状态机、DFA、NFA、正则引擎、lookahead、回溯、错误机制、代码追踪、缓冲区、最长匹配、优先级、关键字识别……你写的时候就感觉:不就应该这样吗?

后面的视频中会手把手教你如何从零写一个词法分析器,边写边解释这些名词

举个例子

比如把这段代码喂给词法分析器

image.png

词法分析器(lexer 或 tokenizer)会将其分解为一系列 token。以下是按顺序产生的 token 列表:

  1. if — 关键字 (IF)
  2. ( — 左括号 (LPAR)
  3. 1 — 整数字面量 (NUMBER)
  4. ) — 右括号 (RPAR)
  5. : — 冒号 (COLON)
  6. print — 标识符(在 Python 3 中是内建函数,不是关键字) (NAME)
  7. ( — 左括号 (LPAR)
  8. 2 — 整数字面量 (NUMBER)
  9. ) — 右括号 (RPAR)
  10. 换行符 — (NEWLINE)
  11. 文件结束 — (ENDMARKER)

语法分析

词法分析器解析程序代码为token,然后丢给语法分析器

语法分析器的输入是token,输出是抽象语法树,即AST

比如代码

image.png

生成的抽象语法树长这样

如果你想得到语法分析器,也是两种方式:1、自实行;2、使用语法分析器生成功能生成

主流的生成工具有这些

我们这套课程使用的是bison

当然,为了让你彻底玩明白语法分析,会带你手写实现一个语法分析器。这个词法分析器,可以与我们自实行的词法分析器配合使用,也可以与flex生成的词法分析器配合使用。语法分析阶段涉及的理论如:上下文无关文法、递归向下分析、LR分析、错误处理……会在手写的过程中详细讲解

抽象语法树AST

后面的所有阶段,都是围绕AST工作的

AST也有开源项目可用

AST是属于手写编程语言的核心了,这个我是自己纯手工打造的。这个还不自己写,那手写编程语言就没有意义了

课程中,我会从零带你手写一个AST,带你彻底哪些AST。

如果你从来没有玩过树这种数据结构,那你通过玩AST,对于你后面去玩数据库领域常用的B tree、B+ tree,算是打开了一扇门

语义分析

语义分析是编译器前端中的关键阶段,位于语法分析之后、IR(中间表示)生成之前。其职责是确保源程序在语义上是合理且一致的。下面是语义分析的完整职责清单,分为核心职责与可选扩展:

✅ 语义分析的完整职责

1. 符号解析(Symbol Resolution)

  • 建立并维护符号表(Symbol Table)
  • 确定标识符(变量名、函数名等)引用的定义
  • 检查变量是否已声明
  • 处理作用域规则(全局、局部、嵌套作用域)

2. 类型检查(Type Checking)

  • 表达式左右操作数类型是否匹配(如 int + float
  • 函数参数与返回值类型是否匹配
  • 条件表达式必须为布尔类型(如 if, while 的条件)
  • 隐式类型转换是否合法(如 int → float

3. 作用域和生命周期检查

  • 名字遮蔽(shadowing)检查
  • 检查是否访问了超出作用域的变量
  • 检查重复定义(如重复变量名)

4. 控制流语义验证

  • returnbreakcontinue 是否出现在合法位置
  • 所有路径是否都返回(对有返回值的函数)
  • 检查不可达代码

5. 函数/方法调用检查

  • 参数个数、顺序、类型是否匹配函数定义
  • 是否调用未声明或未定义的函数

6. 左值(L-Value)/右值(R-Value)检查

  • 赋值语句左边必须是变量(左值)
  • 常量不能被赋值

7. 常量表达式求值(可选)

  • 可在语义分析阶段做初步常量折叠(Constant Folding)
  • 用于宏、数组大小、switch 标签等必须为常量的语法

8. 类型推导(可选)

  • 根据上下文推导表达式、变量、函数返回类型(如 autovar

9. AST 注解(Annotation)

  • 给 AST 节点添加类型、引用的符号等信息
  • 为后续阶段(如优化、代码生成)提供必要数据

10. 语言特性的语义规则

  • 如类的继承检查、重载规则、多态一致性(面向对象语言)

  • 模板/泛型约束检查

  • 模块依赖检查(import/export)

中间代码

完成语义分析后,就代表代码是没有语法上的问题的,可以正常运行,就需要生成中间代码,丢给虚拟机去运行。

你是不是想问:如果是不需要虚拟机运行的编程语言,比如C、C++,也会生成中间代码吗?是的,也会生成。这个生成中间代码是为了生成后续的机器码做优化用的,与依赖虚拟机运行的中间代码是不一样的

中间代码是由中间代码生成器生成的。中间代码生成器有这些类型

我们所熟知的Java、Python,都是栈式IR

比如Python代码

image.png 生成AST

生成栈式IR

是不是这样学习编译原理,理解起来容易很多

课程《手写编程语言》二期,我会教你编写中间代码生成器,将我们自实行的AST,生成Python虚拟机可用的中间代码,让我们自实行的虚拟机,与Python的虚拟机,实现互通。你觉得做起来难不难?

机器码

如果是Java、Python,就没有这个阶段了。比如Java的.class文件,就是Java虚拟机hotspot需要的字节码文件。比如Python的.pyc,就是Python虚拟机cpython需要的字节码文件。字节码转为真正的机器码运行,是虚拟机干的事

机器码长啥样呢

机器码为什么长这样呢?它是按什么规范生成的呢

其实编程语言的优化,大部分都是在机器码层面做文章

比如字节码解释器的执行效率不如模板解释器,原理是什么

原理是编译器会按照固定的框架进行编译生成程序,而模板解释器是一种机器码编制技术,只会生成聚集业务处理的代码。

如图中,如果只是干int a=0,gcc会生成这么多代码,意思就是虚拟机执行这个赋值要执行这么多代码。而模板解释器就是没有打叉的那些,只需要运行那一行有效代码。执行的代码量少这么多,自然效率高很多

当然还有其他的优化机器,你如果真正能看懂这个例子,你自然也能get到,如果你看不懂,那你需要补点机器码的基础了

《手写编程语言》三期,带你使用机器码编织技术重写执行引擎,玩明白了这个,你就能轻松get即时编译及后端优化的各种技术!

至此,速通编译原理就讲完了。如果我文章中的一切你都看懂了,能get到,编译原理你就算过关了

后续还会更新更多超硬核文章,感兴趣的话可以关注**【硬核子牙】**微信公众号