可移植性概述
- 如果一个程序能够以较小的代价移植到另一台机器上,则称该程序是可移植的(Portable)。即:迁移程序的开销显著低于重新编制程序的开销。
- 如果一个程序容易进行修改以适应不同系统和用户的需求,则称该程序是可适应的(adaptable)。
抽象机代码
为了让编译程序具有较高的可移植性和可适用性,一个较好的办法是将硬件资源抽象为抽象机,从而基于抽象机生成硬件无关的中间代码。但抽象机的设计要面向源语言的基本操作和模式,因此是源语言相关的。典型的抽象机代码有Pascal运行在P-machine上的P-code,和Java运行在JVM上的字节码。下面以P-code为例,介绍抽象机代码。
P-machine
P-machine是Pascal使用的抽象机,是一种硬件无关的栈式计算机(stack computer),有五个寄存器和一个存储器:
- 五个寄存器:
- PC:程序计数器;
- NP:堆指针(new指针);
- SP:栈指针;
- MP:标志指针(基地址指针),指向当前活动记录的基地址;
- EP:顶指针,指向当前程序模块在栈中的最高位置。
- 存储器:视为存储单元的线性数组。
- 代码区:存放机器指令;
- 存储区:存放运行数据。
栈式计算机的寄存器和存储器示意图如下:
P-code
一些P-code指令如下:
由于P-machine没有用于存储临时变量的寄存器,所有的运算都在运行栈的栈顶进行。
例子:计算
d
:
=
(
a
+
b
)
∗
c
d:=(a+b)*c
d:=(a+b)∗c
- 取a LOD a
- 取b LOD b
- 相加 ADD
- 取c LOD c
- 相乘 MUL
- 送d STO d
抽象语法树(AST)
和语法树不同,抽象语法树去掉了不必要的语法成分,仅保留操作符(非叶节点)和操作数(叶子节点)。抽象语法树可以看作前述波兰表示、三元式等形式的代码的图表示,遍历抽象语法树就能推出对应的波兰表示或N-元式。
例子:对于
(
A
+
B
∗
C
)
/
(
D
∗
E
−
(
F
+
G
)
/
(
H
+
I
)
)
(A+B*C)/(D*E-(F+G)/(H+I))
(A+B∗C)/(D∗E−(F+G)/(H+I)),按照自底向上的顺序很容易构造出抽象语法树:
与波兰表示的关系
对抽象语法树进行前序或后序遍历,就能得到对应的前缀波兰表示或后缀逆波兰表示(逆波兰表示)。(显然,按中序遍历并加以括号,就能得到原中缀表达式。)
- 前序遍历(根-左-右):
/ + A ∗ B C − ∗ D E / + F G + H I /+A*BC-*DE/+FG+HI /+A∗BC−∗DE/+FG+HI - 后序遍历(左-右-根):
A B C ∗ + D E ∗ F G + H I + / − / ABC*+DE*FG+HI+/-/ ABC∗+DE∗FG+HI+/−/
可以说,波兰表示是语法树的线性表示。
与三元式的关系
以上例子对应的三元式中间代码如下:
① *,B,C
② +,A,①
③ *,D,E
④ +,F,G
⑤ +,H,I
⑥ /,④,⑤
⑦ -,③,⑥
⑧ /,②,⑦
可见:
- 每个三元式表示一棵子树;
- 三元式中操作符表示子树的根;
- 三元式中操作数表示子树的叶子节点(变量名)或另一棵子树的根(另一个三元式的的编号);
- 最后一个三元式的对应AST的根节点。
因此,先前按自底向上而不是自顶向下的顺序来构造AST的好处是:AST的各节点标号正好是三元式各行代码的标号。