欢迎回来
登录你的知识库账户
忘记密码?
还没有账户?立即注册
创建账户
注册你的专属知识库
已有账户?去登录
找回密码
输入注册邮箱获取验证码
返回登录
请输入图片中的验证码以继续注册
加载中...
取消
新建收藏
手动添加你喜欢的内容
取消
编辑头像与昵称
上传新头像或修改你的显示昵称
支持 JPG/PNG,最大 2MB
取消

问题反馈

notebasewww.notebase.cn
控制台
内容库
动态
管理
账户
U
用户
--
在线
v0.8.7 · 知识库
笔记
KnowledgeBase
网络无边,知识有迹。
0笔记
0工具
30推荐

分类导航

按主题直达

编辑精选

站内用户贡献 · 真实笔记

最新收录

每日更新
继续浏览全部内容 →
>
笔记
0
加载中...
工具
0
此页用于记录用户反馈问题后的每一次改进
笔记用法

“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。

md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。

要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。

工具用法

在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。

要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。

工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。

隐藏笔记

写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。

适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。

数据安全

你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。

技术

全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。

理念

这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。

原则

不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。

更多

产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。

举报

如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。

趋势
// 点击导航加载发现
归档
// 归档为空
最近浏览
// 暂无浏览记录
发布
// 加载中...
用户发布
// 加载中...
用户管理
// 加载中...
访问统计
// 加载中...
内容审核
// 加载中...
个人信息
// 加载中...
返回首页

编译器与语言设计入门:从零构建你的编程语言

2026/7/5编程开发

编译器设计核心概念:词法分析、语法分析、语义分析与代码生成的全流程解析。

编译器与语言设计入门:从零构建你的编程语言

引言:为什么学习编译器设计?

编译器是计算机科学中最经典的“系统软件”之一。它不仅是C、Python、Java等高级语言的幕后推手,更是理解计算机如何执行代码的绝佳窗口。很多程序员对编译器的印象停留在“黑盒”阶段——输入源代码,输出可执行文件,中间发生了什么?其实,编译器设计的核心思想并不神秘,它由几个清晰的阶段组成,每个阶段都有成熟的理论和工具支撑。

本文基于Douglas Thain的《Introduction to Compilers and Language Design》一书的核心内容,系统性地拆解编译器的构建过程。无论你是想自己设计一门领域特定语言(DSL),还是想深入理解编译原理,这篇文章都会给你一个扎实的起点。

编译器的主要阶段

一个典型的编译器可以划分为以下五个阶段:

  1. 词法分析(Lexical Analysis):将源代码字符串拆分成有意义的“单词”,即词法单元(Token)。
  2. 语法分析(Parsing):根据语言的语法规则,将Token序列组织成抽象语法树(AST)。
  3. 语义分析(Semantic Analysis):检查AST是否符合语言的语义规则(如类型检查、作用域解析)。
  4. 中间代码生成(Intermediate Code Generation):将AST转换为平台无关的中间表示(IR)。
  5. 代码生成(Code Generation):将IR转换为目标机器码或字节码。

此外,现代编译器通常还包含优化阶段,穿插在中间代码生成和最终代码生成之间,用于提升生成代码的效率。

阶段一:词法分析——把字符串变成Token

词法分析器(Lexer)的任务是读取源文件的字符流,并按照预定义的规则(通常用正则表达式描述)将其切分成Token序列。每个Token通常包含两个信息:类型(如关键字、标识符、数字、运算符)和值(如变量名"x"、数值42)。

例如,对于表达式 a = 42 + b;,词法分析器会输出类似以下的Token序列:

IDENTIFIER("a") ASSIGN INTEGER(42) PLUS IDENTIFIER("b") SEMICOLON

关键实现细节

  • 正则表达式:每个Token类型对应一个正则模式,词法分析器使用有限自动机(DFA,确定性有限自动机)来高效匹配。例如,标识符的模式可能是 [a-zA-Z_][a-zA-Z0-9_]*,整数字面量的模式是 [0-9]+。
  • 最长匹配原则:当多个模式可能匹配时(如if既是关键字,也是标识符),词法分析器通常选择最长的匹配。如果长度相同,则按模式定义的优先级选择(关键字优先于标识符)。
  • 跳过空白与注释:空格、换行、注释通常不产生Token,而是被词法分析器直接忽略。

工具支持

手动编写词法分析器是可行的,但更常见的做法是使用自动生成工具,如Lex(或Flex)和Ragel。你只需提供一组正则表达式和对应的动作代码,工具就会生成高效的C、C++或Rust词法分析器。

阶段二:语法分析——构建语法树

语法分析器(Parser)将Token序列作为输入,根据上下文无关文法(Context-Free Grammar, CFG)构建出抽象语法树(AST)。AST是源代码的树形表示,去除了括号、分号等语法细节,只保留结构关系。

上下文无关文法简介

CFG由一组产生式(Production)组成,每个产生式描述如何将非终结符替换为终结符或非终结符的组合。例如,一个简单算术表达式的文法可以写成:

expr -> expr '+' term | term
term -> term '*' factor | factor
factor -> '(' expr ')' | NUMBER

这里,expr、term、factor是非终结符,+、*、(、)、NUMBER是终结符。

解析方法

  • 自顶向下解析(如递归下降解析器):从起始符号(如expr)开始,尝试匹配输入Token序列。每个非终结符对应一个函数,函数内部根据当前Token选择对应的产生式。递归下降解析器易于手动编写,但要求文法不能有左递归(如expr -> expr '+' term会导致无限递归)。
  • 自底向上解析(如LR解析器):从输入Token开始,逐步归约为非终结符,最终归约为起始符号。LR解析器能处理更广泛的文法(包括左递归),但通常需要借助Yacc/Bison等工具自动生成。

抽象语法树(AST)设计

AST的节点类型通常与文法中的非终结符对应。例如,对于加法表达式expr '+' expr,AST可能包含一个AddNode,其左子节点和右子节点分别是两个子表达式。AST不包含括号、分号等语法糖,只保留运算的层次关系。

阶段三:语义分析——为AST赋予意义

语法只检查代码的“形式”是否正确,语义分析则检查代码的“含义”是否合法。主要工作包括:

  1. 类型检查:确保操作数类型与运算符兼容。例如,在大多数静态类型语言中,"hello" + 42是不合法的。
  2. 作用域解析:确认每个变量引用都指向一个已声明的变量,且类型匹配。
  3. 控制流检查:例如,检查break语句是否出现在循环内,函数是否在所有路径上都有return语句。

符号表

语义分析的核心数据结构是符号表,它记录了每个作用域中声明的变量、函数、类型等信息。符号表通常以栈的形式实现:进入一个新作用域时压栈,退出时弹栈。每个符号条目包含名称、类型、作用域深度、存储位置等属性。

类型系统

类型系统可以是静态的(编译时检查)或动态的(运行时检查)。在编译器中,类型检查通常基于类型推导或类型标注。例如,在ML或Haskell中,编译器可以自动推导出表达式的类型,而在C或Java中,程序员需要显式声明类型。

阶段四:中间代码生成——平台无关的桥梁

中间表示(IR)是编译器中至关重要的一层。它比AST更接近机器码(通常是三地址码或静态单赋值形式SSA),但又与具体CPU架构无关。使用IR的好处是:

  • 可以在IR层面进行跨平台的优化(如常量折叠、死代码消除)。
  • 后端代码生成器只需将IR翻译成目标机器码,无需重复实现词法、语法分析。

三地址码(Three-Address Code, TAC)

三地址码的每条指令最多包含三个操作数,例如:

t1 = 42
t2 = b
t3 = t1 + t2
a = t3

这种形式非常适合后续的优化和寄存器分配。

静态单赋值形式(SSA)

SSA是更高阶的IR,要求每个变量只被赋值一次。通过引入“Phi函数”(φ函数)来处理控制流汇合点的变量版本,SSA使得数据流分析更加简单高效。现代编译器(如LLVM、GCC)都使用SSA作为主要IR。

阶段五:代码生成——从IR到机器码

代码生成器将IR转换为目标CPU的汇编指令或机器码。这一阶段涉及多个关键子问题:

  1. 指令选择:为每条IR指令选择合适的机器指令序列。例如,t1 = t2 + t3可能对应ADD R1, R2, R3(假设寄存器分配已完成)。
  2. 寄存器分配:将IR中的虚拟寄存器映射到物理寄存器。由于物理寄存器数量有限(如x86有16个通用寄存器),需要利用图着色算法或线性扫描算法来优化分配。
  3. 指令调度:重新排列指令顺序以利用CPU的流水线特性,减少停顿(stall)。

寄存器分配:图着色算法

寄存器分配可以建模为图的着色问题:每个虚拟变量是图的一个节点,如果两个变量在程序中的某个点同时活跃,则它们之间有一条边。用K种颜色(对应K个物理寄存器)给图上色,相邻节点不能同色。如果K种颜色不够,就需要将某些变量“溢出”(spill)到内存中。

优化:让代码跑得更快

优化可以在编译器的多个阶段进行,包括:

  • 常量折叠(Constant Folding):在编译时计算常量表达式,例如3 + 5直接替换为8。
  • 死代码消除(Dead Code Elimination):删除永远不会执行的代码,例如if (false) { ... }。
  • 循环优化:如循环展开(loop unrolling)、循环不变代码外提(loop-invariant code motion)。
  • 内联展开(Inlining):将小函数的调用替换为函数体本身,减少调用开销。

实战:用Flex和Bison构建一个微型编译器

书中提供了一个完整的微型语言示例,称为“C-”(C minus),它包含整数、布尔、数组、函数和基本控制流。构建过程如下:

  1. 用Flex定义词法规则(标识符、关键字、运算符等)。
  2. 用Bison定义语法规则(表达式、语句、函数定义等),并生成解析器。
  3. 在解析过程中构建AST(Bison的动作代码中创建节点)。
  4. 遍历AST进行语义分析(类型检查、符号表维护)。
  5. 生成三地址码(或直接翻译成简单的虚拟机指令)。
  6. 最终输出汇编代码或字节码。

示例:C-语言代码片段

c
int gcd(int a, int b) {
while (a != b) {
if (a > b) {
a = a - b;
} else {
b = b - a;
}
}
return a;
}

经过编译器处理后,会生成对应的三地址码序列,然后翻译成x86汇编。

拓展阅读与工具

  • 经典教材:《编译原理》(龙书)、《现代编译原理》(虎书)、《工程编译原理》(鲸书)。
  • 现代编译器框架:LLVM(提供完整的IR、优化和代码生成基础设施)、GCC。
  • 在线资源:Douglas Thain的《Introduction to Compilers and Language Design》全书免费在线阅读(https://dthain.github.io/books/compiler/)。

结语

编译器设计并非高不可攀的技术。通过理解词法分析、语法分析、语义分析、中间代码生成和代码生成这五个核心阶段,你可以从零开始构建一门简单的编程语言。更重要的是,这种理解会让你对高级语言的运行机制有更深的洞察——无论是调试一个奇怪的bug,还是优化性能瓶颈,编译器知识都能派上用场。

原文链接:https://dthain.github.io/books/compiler/

编写使用方法
Markdown 格式 · Ctrl+Enter 确定
新建笔记
预览
数据表格
点击单元格编辑 · Tab 移动
A1fx
Sheet1
BIH1H2≡🔗</>
隐私提醒

取消
编辑工具
取消