看的编译原理,发现挺好玩的东西:自举和移植。
有个著名的问题:Mommy, where do compilers come from?要解决这个问题,首先来看看T-Diagram。可以将编译器用一个T形图来表示:
---------
| S T |
--- ---
| I |
---
其中,S表示Souce Language,T表示Target Language,I表示Implementation Language。
根据这个图,可以得到两种组合:
1、 由I实现的将S翻译成A的编译器和将A翻译成T的编译器联合起来工作,可以实现S到T的编译。这不是很有意思的组合。
------------------
| S A | A T |
--- ------ ---
| I | | I |
--- ---
2、由I实现的将S翻译成T的编译器(compiler C1),由H实现的将I翻译成K的编译器的组合(compiler C2)。这个组合比较有意思:可以用C2去编译C1(将其实现语言从I翻译成K),这样就得到由K实现的将S翻译成T的编译器(compiler C3):
--------- ---------
| S T | | S T |
--- ---------- ---> --- ---
| I | I K | | K |
------- --- ---
| H |
---
这便是我们所感兴趣的:编译器的编译器。
而一个程序要在某台机器上运行时,其实现语言必须与相应的机器语言匹配。例如在x86平台上,能执行的程序只能是x86机器语言:
------------------
/ Program /
/ /
-------------
| X86 |
-------------
| X86 |
-------------
让我们将T形图和上图联系起来看,以C为例(假定已有x86的C编译器):
-------- -------- ---------
/ Hello / / Hello / / Hello /
----------------------- -------
| C | C x86| x86 | ---> | x86 |
--------- ---------- -------
|x86| | x86 |
---- -------
上图表示用C写的Hello程序可以在C“机器”上运行,然而这不是我们想要的——在x86机器上运行。于是使用实现语言为x86的编译器(其可执行代码)编译形成x86语言,这样就可以运行了。
根据上面的第2条(编译器的编译器)以及程序的执行,我们可以得出,要想得到在M机器上运行的S语言编译器,可以使用S语言来编写(用M语言也可以,然而太麻烦)。但还是需要用M语言写一个具有很小功能的S编译器,然后用其去编译加入了更多功能的编译器,然后用编译出来的更多功能的编译器去编译,这样就得到了最终需要的编译器,这便是自举。
--------- ---------
| S M | | S M |
--- ---------- ---> --- ---
| S | S M | | M |
------- --- ---
| M |(微型S编译器)
---
用S编写S的编译器还有一个好处,那就是方便移植,并且能产生交叉编译器。假定要编译K机器语言的S编译器,只需要将目标语言替换成K,然后用已有的S语言编译器来编译:
--------- ---------
| S K | | S K |
--- ---------- -> --- ---
| S | S M | | M |(运行在M机器上的编译器,即交叉编译器)
------- --- ---
| M |
---
有了交叉编译器,就可以编译在K上运行的S编译器了:
--------- ---------
| S K | | S K |
--- ---------- -> --- ---
| S | S K | | K |
------- --- ---
| M |
---
编译原理--编译器的自举与移植
最新推荐文章于 2023-01-12 12:23:59 发布