编译器"自举与移植"原理
本文基于对《编译原理与实践》中有关编译器自举与移植部分的读书“笔记”形式,因为原书是老外写的,感觉翻译的地方好多语句不通或难以理解,所以花了好多功夫研究这一块。注:本文中与原书一致的地方都是PDF截图。
这段话的意思是说,最开始的时候,没有任何编译器,也就是说即使有高级语言,比如C语言,也是不能编译的,更不用说运行了。所以最开始的编译器是用汇编(或机器语言)写的。而且编译器存在的形式必须是机器码形式--只有机器码才能得到机器的执行,从而能够将“高级语言”作为输入,将其翻译为机器码。但是,现在有了高级语言和对应的编译器,我们就可以采用这种高级语言去编写新的语言(或者就是这种语言本身)的编译器了--只要用这种语言,比如C语言,去写完编译器的源代码,然后用编译器将其翻译为本地机器码,就可以了。比如,现在机器A上只有C语言编译器,我们想开发一种新的语言Java及其编译器,我们可以用C语言去完成java编译器的编写工作,然后将其用C语言编译器便以为本地可执行机器码,我们就得到了java编译器。这里,还要说一下,为什么C语言写的java编译器可以处理java源文件呢?其实,无论是什么语言的源文件,比如C语言的源文件xx.c,C++的源文件xx.cpp,还是java的源文件xx.java...它们本质上都是具有一定格式和顺序的文本文件,再说的本质点,它们在计算机内都是010101式的字符流,再再本质一点,它们就是计算机内存中的高低电平。但还是理解为普通的文本文件就可以了。而编译器呢?当其作为机器码在本地运行时,这些源文件就是作为编译程序的输入,不管是哪种语言的源文件,都可以被任意的编译程序作为输入,但是特定语言的编译器只认识和编译具有特定语法格式的源文件,也就是说,C语言编译器的输入源文件可以是任意的文本,但是他只认识和编译具有C语言语法格式的文本。。。因为这种编译器只是为这种语言设计和实现的。
以上这段话,指出了另一个存在的问题---当我想在另一种机器B(不同于机器A)上产生java编译器,但机器B上还没有C编译器的存在,我们就需要产生一个交叉编译器:在这里就是一个在机器A上运行,但最终翻译得到的不是机器A的机器码而是机器B的机器码的一种编译器,这样,由这个所谓的“交叉编译器”编译原本用C语言完成的java编译器,得到的机器码copy到机器B上就得到了可以在机器B上运行的java编译器。
之后,原文中为了解释“交叉编译器”的原理,介绍了T型图,在我的笔记中,将上文中T型图整体称为编译器体系,S部分称为编译器的输入,H部分称为编译器的数据处理中心,其实现语言为执行语言,T部分称为编译器的输出。即T型图抽象了一个编译器系统,有输入,有处理,也有输出。简单的举两个T型图的实例:1.C语言编译系统--输入为C语言源文件xx.c,处理中心为本地编译器的可执行机器码,输出为最终翻译得到的本地可执行机器码形式。2.还是C语言编译系统,只不过输出部分有所变化--将输出变为了汇编程序的形式,然后再用另一个“编译系统”将此汇编程序文件作为输入,产生最终的可执行机器码。
以上这一段介绍了T型图的一种组合方式,我觉得可以称为横向水平组合,本质上就是将一个编译系统的输出B作为另一个编译系统的输入(前提是前者的输入可以作为后者的输出,至于两个编译器是否一定要在同一台机器或者同一类型的机器上运行,我觉得是不必要的)。在上文我的笔记中介绍T型图的时候,举得第二个例子就是对应这种情况。
编译系统“T型图”的另一种组合方式--我觉得有点“交叉”的感觉。这种方式是借助了另一个编译系统(就是图中以M为处理中心的T型图)--我觉得这就是所谓的交叉编译器(也就是原文中“由机器H到机器K的编译器”,至于原文中的后半句“翻译由H到K的其他编译器的执行语言”,感觉读不懂,但我觉得原文如果是“翻译由H到K的其他机器的执行语言”,但“机器”和“编译器”从某种层次上其实是一个意思。)。原文可以改写成“利用‘机器’H到‘机器’K的编译器M(T型图)来翻译由H到K的执行语言“,我局的意思就是说用M将H翻译为K。从而得到一个由K语言编写的能将A翻译成B的编译器。本质上,这种组合就是利用交叉编译器,将已经存在的一种编译系统T型图中的处理中心的执行语言转化为另一种执行语言--重点是,偷天换日,将编译系统的执行语言换了。
这段话也是难以理解。先来理解”在机器H上利用B语言现存的编译器“,”在机器H上“--说明本段建立在这样的一个事实之上--只存在一种机器H。这也就奠定了图中两个编译器的运行环境,就是这台机器,也就是说其执行语言最终是机器码,没有这种以机器码为执行语言的编译器,将是不可直接运行的,更谈不上编译了。所以”在机器H上利用B语言现存的编译器“,就是说这台机器上存在以机器码为执行语言的B的编译器。图中第一个T型图所表示的编译系统在机器H上是无法直接运行,因为其执行语言是B语言而不是机器语言。所以要利用图中第二个T型图表示的编译系统,将B语言翻译为本地机器码,从而得到一个A语言的以本地机器码为执行语言的编译器。原文中 ”将语言A翻译为用B语言编写的语言H“,这句话真的是难以读懂。我觉得这句话有误,略过,也不影响本文的分析过程和完整性。上图所示过程的本质是,利用一种以本地机器码为执行语言的编译系统,将一个以非本地机器码(在这里就是高级语言,比如汇编语言,C语言)为执行语言的编译系统(这种编译系统在机器上是不能独立和直接运行的,因为机器只能识别和执行本地机器码)的执行语言翻译为本地执行语言机器码,上图中就是将第一个编译系统的执行语言B替换为了本地执行语言H。从而可以达到原编译系统依靠B语言可以直接工作的假象。
举个实际的例子,假设存在一种语言A的编译器,可以将语言A翻译为语言H--假设这样的一种编译系统:输入为语言A,编译器进行翻译处理,然后得到输出语言H。我们以C语言实现了这种语言A的编译器。假设编译器的源文件我们写好了,叫做A.c,现在,有一个文本文件A.txt,理论上,经过A.c的”翻译“,可得到输出文件H.txt。这就构成了上图中的第一个T型图所表示的编译系统,输入文件为A.txt,编译器的执行语言为C语言(就是A.c的内容,C语言的源代码形式),输出文件H.txt。但是,这个编译系统是不能直接运行和编译的--A.c得不到机器的直接执行,因为机器只识别和运行本地机器码(虽然A.c文件本质上和机器码在存储形式上没有任何区别,都是01序列,但是他们的形式和格式不一样)。所以,要利用机器上已经存在的C语言的编译器(其执行语言为本地机器码的编译系统),将A.c翻译成本地机器码语言的形式A.h,从而A.h可以在机器上直接运行,并以A.txt作为输入,将其翻译为H.txt。
这第二个假定不同于第一个假定之处在于,存在两种不同的机器。这就引出了交叉编译器的概念,交叉编译器就是一个能生成不同于它所运行的机器之上的目标代码的编译器。说白了就是在Intel机器上运行并生成另一个机器Arm上的目标代码。关于交叉编译器,最后再细讲。
这段话,这么解释,比如,用C语言编写C语言的编译器,这种方法是很普遍的。但这会变现一个循环错误,后文也提到了,如果源语言的编译器不存在,那么编译器本身也就不可能被编译了,解决的方法就是用机器码或汇编产生一个简单的C语言编译器。见下图:
以上内容较为易于理解,就不多说了。
这里所说的源代码后端,指的应该是比如用C语言写的C语言编译器的源代码,只要将源代码中的”后端“修改为生成指定的机器代码,然后用旧的编译器编译就可以得到一个交叉编译器,因为此编译器和旧编译器不同,虽然都运行在同一台机器上,但新的编译器生成另一种机器的目标代码。从而,用此新的编译器编译修改”后端“后的编译器源代码,可以得到一个可以在另一台机器上运行的编译器。