在 Java 和 C# 以及许多使用基于堆栈虚拟机的常见编程语言和运行时中,我们编写基本的赋值表达式就像这样简单:
a = 1;
编译器最初生成(伪)汇编代码(或中间语言),如下所示:
PUSHINT 1 # push 1 into the top of evaluation stack
DUP # duplicate the top of evaluation stack
ST a # store the value at the top of evaluation stack to variable a
DROP # drop the top of evaluation stack
而不是
PUSHINT 1
ST a
我认为原因是,
a=1
是一个返回值为1的表达式。这用于连续赋值,例如a=b=1
。然而,我们不能总是接受到处都是冗余的 DUP 和 DROP。
优化后的汇编代码中多余的DUP和DROP是如何自动删除的?
几乎所有基于堆栈的虚拟机都有所谓的堆栈帧 - 有关堆栈在某个时间点包含哪些值的信息。这就是为什么您不能在循环中将元素推入堆栈的原因 - 堆栈值需要与堆栈帧类型对齐。该堆栈帧又允许进行大量优化,但最简单的示例是将推送的值分配给表示堆栈空间的变量。我们可以这样做的原因是,由于堆栈帧声明,对于每次堆栈修改,我们都提前知道堆栈的结构。在你的情况下,这可能看起来像这样:
PUSHINT 1 -> stack1 = 1
DUP -> stack2 = stack1
ST a -> a = stack2
DROP -> # do nothing
从这个形式中,无用的
stack1
和stack2
可以很容易地被优化掉,我们只得到a = 1
。