跳到主要内容

编译器和解释器的区别

· 阅读需 8 分钟
古时的风筝

前两天看到一个「用200行代码实现编译器」的 GitHub 仓库。这个微型编译器用 JavaScript 实现,最终实现了将类似 Lisp 语法的函数编译为类似C语言的函数。

编译器的作用是什么?就是将一种语言源代码转换成目标语言,这个目标语言可以是另外一种语言,比如 C 语言;可以是中间语言,比如 Java 字节码;还可以直接就是机器码。

作者就是借助 Lisp 的语法格式,以及 C语言的语法格式,将下图中红框中的代码转换为蓝框中的代码。一眼看上去好像很简单,但是编译过程还是很复杂的,如果你之前买过有关编译原理的书,看那书的厚度就应该能看出来。

仓库地址:https://github.com/jamiebuilds/the-super-tiny-compiler

编译原理的过程虽然复杂,但是这么多年来一直都有统一的标准流程,不管是多么简单、多么复杂的编译器一直都是下面这个流程。

  1. 词法分析(Lexical Analysis):编译器首先会对源代码进行词法分析,将代码分解成词法单元(tokens),比如关键字、标识符、运算符等。

  2. 语法分析(Syntax Analysis):接着,编译器会进行语法分析,将词法单元组织成语法结构,通常使用语法树(Syntax Tree)或者抽象语法树(Abstract Syntax Tree,AST)来表示代码的结构。

  3. 语义分析(Semantic Analysis):编译器在执行之前会进行一些语义分析,检查代码是否符合语言的规范,并做一些必要的转换,比如类型检查、变量解析等。

  4. 中间代码生成(Intermediate Code Generation):接下来,编译器会将语法树或者AST转换成中间代码,这种代码通常比较接近目标机器的汇编语言,但是比较容易优化和转换。

  5. 优化(Optimization):在生成中间代码的过程中,编译器可能会进行一些优化,比如消除冗余代码、提取公共表达式等,以提高目标代码的性能和效率。

  6. 目标代码生成(Target Code Generation):最后,编译器将优化后的中间代码转换成目标机器的机器码或者字节码,这样就可以在目标机器上执行了。

如果哪天编译过程能够跳出这个流程,很有可能计算机技术又跃升了一个级别,说不定是下一次技术革命的时候呢。

关于 Lisp

很多关于编译原理的书都会提到 Lisp 语言,包括上面这个200行的编译器,也借助了 Lisp 的语法。如果你看过《黑客与画家》这本书,那我相信你很有可能了解过 Lisp 语言,这本书的作者、大名鼎鼎的硅谷创业之父Paul Graham在这本书中对 Lisp 赞赏有加、推崇备至。

直到我开始了解 Lisp ,我才发现为什么以前的程序员技术都比较好,看一段 Lisp 实现的斐波那契数列的实现。

(defun fibonacci (n)
(cond ((= n 0) 0)
((= n 1) 1)
(t (+ (fibonacci (- n 1))
(fibonacci (- n 2))))))

而用 Java 是这样的

public static int fib(int n) {
if (n == 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}

Lisp 的语法可谓是没有语法,没有那么多花里胡哨的,就是操作符跟着操作数,跟汇编比较相像。比如实现 1+1,那就是 + 1 1,实现2*5,那就是 * 2 5

还有一个特点就是括号,括号控制优先级。有个笑话说的是苏联黑客偷到了美国用于导弹控制的代码的最后一页,可能能看出一部分逻辑,很可惜的是Lisp代码,这最后一页全是右括号。

Lisp 这种语法你往深里想一下,那真是既酸爽又惊悚啊,就每天写这个,能不厉害吗。而且这种简单粗暴的写法,编译器是很喜欢的,解析起来比较简单一些。

推荐两本书

对于我来说,学编译原理的目的很简单,就是了解一下代码是怎么样被解释的、怎么样被编译的、最后如何执行的,仅此而已。要说学完能自己写个编译器,那不好意思,确实没那个实力。

如果你也是同样的目的,我读过的两本比较简单、读起来不那么枯燥的书推荐给你读一下。

一本是关于解释器的,一本是关于编译器的,建议先读读解释器原理的,解释器和编译器有一部分是相同的,但是解释器更简单,也更容易理解。了解了解释器,再去看编译器,能省不少力气。

《用Go语言自制解释器》

这本书,作者一步步的实现了一个叫做 Monkey的语言,名字是作者自己起的,语法很简单,稍微有语言基础的同学都能一眼看明白。

而解释器使用 Go 语言实现的,也就是你用 Monkey语言写代码,当代码要执行的时候,负责解释的是 Go 语言。

比如 let s = 25*2+3 这一行代码,如何让 Go 实现的解释器识别出来,并变成 Go 语言的语法实现,最终将运行结果返回给 Monkey语言的。

读完之后,当你在 Python 命令行中输入 2+2时,最终是如何输出 4 这个结果的,这都归功于背后的 C 语言实现的解释器。

《自制编译器》

这本书,作者也实现了一个语言,叫做 Cb语言,可以认为是 C 语言的子集,从头开始一步步讲解 Cb语言是如何被词法分析、语法分析、抽象语法树、代码生成以及最终编译出的结果如何被链接和执行的。

风筝

作者

风筝

古时的风筝,一个平庸的程序员,主语言 Java,第二语言 Python,其实学 Python 的时间比 Java 还要早。喜欢写博客,写博客的过程能加深自己对一个知识点的理解,同时还可以分享给他人。喜欢做一些小东西,所以也会一些前端的东西,React、JavaScript、CSS 都会一些,做一些小工具还够用。