对于我的作业,我需要在C#中编写一个非常小的虚拟16位汇编程序 - 解释器。 它使用字节数组(64k)模拟RAM,使用变量(A,B,C,...)模拟寄存器。 现在我需要一种保存局部变量的方法,google说它们是在Stack上分配的。
但是我不清楚的是,当它们被分配到堆栈上时(使用push ...),解释器在以后使用时如何访问它们?
请参阅以下两行:
pi INT 3
mov A, pi
在第一行中,pi在堆栈上分配,在第二行中使用pi,但是解释器应该如何知道堆栈中pi在哪里访问其数据? (我的Stack也是一个字节数组,有2个辅助函数(push,pop),还有一个指向堆栈顶部的指针)
答案 0 :(得分:1)
通常没有单独的堆栈内存,而是堆栈在常规RAM中,因此您只有堆栈指针可以跟踪它。
通常,通过将堆栈指针复制到另一个寄存器,然后移动堆栈指针为变量腾出空间,在子例程的开头分配局部变量:
mov bp, sp ;copy stack pointer
sub sp, 4 ;make room for two integer variables
使用堆栈指针的副本来访问局部变量:
mov A, [bp-2] ;get first integer
mov B, [bp] ;get second integer
当您离开子例程时,您将恢复堆栈指针以取消分配局部变量:
mov sp, bp ;restore stack
ret ;exit from subroutine
您在问题中使用的语法通常用于声明全局变量,而不是局部变量:
.data
pi int 3 ;declare a label and allocate room for an int in the program
.code
mov A, pi ;use the address of the label to access the int
答案 1 :(得分:1)
通常,堆栈数据是通过stack pointer
相对访问的,myadd:
push bp ; we'll be accessing stack through bp (can't do that through sp because there's no sp-relative memory addressing in 16-bit mode), so, let's save bp first
mov bp, sp ; bp is equal to the stack pointer
mov ax, dword ptr [bp + 4] ; load ax with 1st parameter stored at bp+4 (sp+4)
add ax, dword ptr [bp + 6] ; add to ax 2nd parameter stored at bp+6 (sp+6)
pop bp ; restore bp
ret ; near return to the caller at address stored at sp (address after call myadd), the result/sum is in ax
是一个CPU寄存器,指向存储在堆栈中的最后一个元素。您可以将其视为模拟CPU内存的索引。每当你将某些东西推到堆栈上时,堆栈指针就会根据该东西的大小递减,并且在递减之后,某些东西会被存储在地址的仿真内存中。无论何时从堆栈中弹出一些东西,都会从存储在堆栈指针中的地址中获取值,然后堆栈指针会根据该内容的大小递增。这就是CPU堆栈在许多不同CPU中的工作方式。
如果您正在实现CPU仿真器或CPU指令仿真器/解释器,那么您并不关心变量。您关心的是操作CPU寄存器和内存的CPU指令,因为您的程序是根据CPU指令表示的。它们(指令)必须跟踪存储在堆栈中的所有loacal变量,即它们相对于堆栈指针当前值的位置。
例如,如果你考虑一个简单的子程序,在栈上添加两个16位整数值,它可能看起来就像这样。 16位x86汇编:
push word 2 ; prepare/store 2nd parameter on the stack
push word 1 ; prepare/store 1st parameter on the stack
call myadd ; near call, pushes address of next instruction (add), jumps to myadd
add sp, 4 ; remove myadd's parameters (1 and 2) from the stack
; ax should now contain 3
调用者可能看起来像这样:
{{1}}
答案 2 :(得分:0)
'google说它们是在Stack'上分配的
这是在真实计算机中实现的方式,但这不是全部。
如果您想要虚拟解释器,则需要使用名为“Hash Table”的数据结构。
这是一个家庭作业问题。所以没有直接答案:P 但是下面的代码将解释如何使用哈希表。将变量名称和值存储在哈希表中。
using System;
using System.Collections;
class Program
{
static Hashtable GetHashtable()
{
// Create and return new Hashtable.
Hashtable hashtable = new Hashtable();
hashtable.Add("Area", 1000);
hashtable.Add("Perimeter", 55);
hashtable.Add("Mortgage", 540);
return hashtable;
}
static void Main()
{
Hashtable hashtable = GetHashtable();
// See if the Hashtable contains this key.
Console.WriteLine(hashtable.ContainsKey("Perimeter"));
// Test the Contains method. It works the same way.
Console.WriteLine(hashtable.Contains("Area"));
// Get value of Area with indexer.
int value = (int)hashtable["Area"];
// Write the value of Area.
Console.WriteLine(value);
}
}
答案 3 :(得分:0)
答案是:这取决于。作为语言设计者,您应该定义什么是可见性(如果定义了变量名,源代码的哪一部分是可用的名称?)和隐藏(如果在另一个对象的可见区域中定义了另一个具有相同名称的对象,哪个名称胜出?)变量规则。不同的语言有不同的规则,只需比较Javascript和C ++。
所以,我会这样做。 (1)引入 namespace 的概念:在源文件的某个点可见的名称列表。 (请注意,这与C ++的命名空间概念不同。)命名空间应该能够将名称解析为某个适当的对象。 (2)当解释器从一个过程更改为另一个过程,从一个文件更改为另一个文件,从一个块到另一个块,看到声明或块结束等时,实现更改名称空间的规则。
这些步骤基本上对大多数语言都有效,而不仅仅是汇编程序。
(我认为,Google对“堆栈分配”的引用是指在一个单独的子例程中处理每个子例程,并在本地重新定义命名空间的概念,因此“在堆栈上”,因此它将在程序时自动弹出饰面。)