我正在创建一个解释器(一个字节码解释器,所以也是一个编译器),我发现了一个我无法解决的问题。我需要在某处存储变量。将它们存储在字典中并在运行时查找它们会变慢,所以我想将它们存储在寄存器中并使用它们的索引而不是它们的名称。
所以在编译时我给每个变量一个索引,并创建一个寄存器数组。对于单片范围的语言来说这很好。但我正在创建解释器的语言有嵌套的范围(和函数调用)。所以另一种方法可能是我有一组全局寄存器和一堆函数调用的寄存器列表。所以我的虚拟机会有类似的东西:
Register globalRegisters[NUMBER_OF_GLOBALS];
Stack<Register[]> callstack;
但还有另外一件事。我的语言允许函数内部的函数。例如:
var x = 1;
function foo() {
y = 2;
function bar() {
z = 3;
y = y - 1;
}
}
Function bar()引用属于foo()的变量。这意味着虚拟机必须查看堆栈顶部的寄存器列表。但是如果bar()是递归的呢?如果用户输入定义了递归次数怎么办?然后虚拟机就不知道有多少堆栈元素必须找到包含y值的寄存器集。
这个问题的有效解决方案是什么?这是我第一次处理寄存器,计算发生在一个值栈上。
答案 0 :(得分:1)
表示闭包的常用方法是创建一个包含函数指针本身及其环境的结构。表示环境的最简单方法是作为指向外部函数的堆栈帧的指针。当访问外部函数的变量时,内部函数可以简单地取消引用该指针(当然具有给定变量的偏移量)。
但是还有另外一个问题需要考虑:如果foo
返回bar
,然后在bar
已经返回后调用foo
,会怎样?在这种情况下,指向foo
的堆栈帧的指针将是无效的,因为该堆栈帧将不再存在于该点。因此,如果您想要允许该场景(而不是简单地使其返回函数非法),则需要另一种解决方案。
一个常见的解决方案是将所有局部变量表示为堆分配值的指针,并在函数结构中存储所有这些指针的副本。
另一个解决方案是限制闭包,以便外部函数的变量只有在它们永远不会被重新分配时才能被内部函数访问。这就是Java 8中的闭包。然后结构可以只包含变量的副本而不是指针。
答案 1 :(得分:0)
我认为这里的基本问题与你所写的基本问题有很大的不同,所以我已经写了一个解释,为什么我认为这个问题是错误的,并且是我认为基本问题的答案。如果我弄错了,我道歉,但请跟我一点: - )
我正在创建一个解释器(一个字节码解释器,也是一个编译器)
解释器不是编译器,即使它是低级语言 - 除非你的意思是你的程序将某些语言编译成某些字节码解释,然后解释它。在任何情况下,除非你正在编写代码,否则实际运行的程序就是解释器。
将它们存储在字典中并在运行时查找它们会变慢,所以我想将它们存储在寄存器中并使用它们的索引而不是它们的名称。
在翻译中,强制将目标语言变量放入寄存器并不适合我。例如,假设您有一种方法来解释使用变量的特定语句。您可以快速提取变量,因为您强制它们进入寄存器,但是您的寄存器太少,无法有效地在您自己的方法中运行操作。而且,说&#34;是的,我只是将它们存储在寄存器中。让我怀疑你高估了你可以使用的寄存器数量。
我猜测&#34;寄存器&#34;这是一个误解,你只关心存在和访问本地的一些有效方法,存在嵌套的范围和递归。所以我认为你的问题可以用来表达&#34;我想要一些存储本地的数据结构,如何在存在嵌套作用域和递归函数的情况下做到这一点?&#34;如果我错了,我很抱歉,但如果没有:
回答&#34;我想要一些存储本地的数据结构,如何在存在嵌套作用域和递归函数的情况下这样做?&#34;我认为最好先澄清区别范围和框架之间,在当地人的背景下。
范围是标识符到局部变量的一些映射。在范围内,您知道x
标识符的所有实例都引用相同的内容(粗略地)。当您解析输入语言时,范围是您关心的 - 它是您用来理解代码语义的内容(&#34;哦,编码器正在递增的x
是2行前的x
&#34;)。
frame 是调用函数时分配的内存(通常在堆栈上)。每个本地通常在框架上获得一个保留位置以存储其值。
当你解析代码,处理范围时,你不关心递归(因为你没有运行任何东西,只是解析)。您确实关心嵌套作用域,但这些作用域永远不会被解除 - 因为代码本身(不是它的执行,只是代码)总是有限的。解析时处理作用域中的本地的标准方法是保留字典堆栈。打开范围时创建并推送新字典,在关闭范围时弹出。每当访问x
时,在堆栈中最顶层字典的字典中查找它 - 如果没有,继续下一个,依此类推。
您生成的代码(或在解释器中直接执行)将准确知道每个x
实例所指的位置。并且在创建帧时将分配这些存储器位置。这样你也不关心递归 - 你已经将变量映射到当前帧中的位置,无论从哪个帧调用,它都是有效的。
在我现在可以回想起的所有语言中,闭包通过在定义时捕获封闭变量来工作。例如,在Java中,在属于外部类的内部类中访问的每个本地在实践中将在其创建时传递给内部类 - 将其视为内部构造的另一个参数类。 C ++更明确地说明了它捕获的变量,但是它的工作方式是相同的 - lambda对象只是在创建时传递给它的那些变量(通过值或通过引用,取决于指令)。在任何情况下,捕获的对象在捕获后都与原始对象不同(它们可能都是指向同一个地方的指针,但不会使它们成为同一个对象),因此它不应该难以解析。