【问题标题】:ASM Interpreter: How are local variables stored?ASM 解释器:如何存储局部变量?
【发布时间】:2023-03-15 13:39:02
【问题描述】:

为了我的作业,我需要用 C# 编写一个非常小的虚拟 16 位汇编程序解释器。 它使用字节数组 (64k) 模拟 RAM,并使用变量 (A,B,C,...) 模拟寄存器。 现在我需要一种方法来保存局部变量,谷歌说它们是在堆栈上分配的。

但我不清楚的是,当它们在堆栈上分配时(使用 push...),解释器在以后使用它们时如何访问它们?

见以下两行:

pi INT 3
mov A, pi

第一行,pi 分配在栈上,第二行使用了 pi,但是解释器应该如何知道 pi 在栈中的什么位置来访问它的数据呢? (我的 Stack 也是一个字节数组,有 2 个辅助函数(push、pop),还有一个指向栈顶的指针)

【问题讨论】:

    标签: c# assembly local


    【解决方案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
    

    【讨论】:

      【解决方案2】:

      通常,堆栈数据是通过stack pointer 相对访问的,stack pointer 是一个 CPU 寄存器,指向存储在堆栈中的最后一个元素。您可以将其视为模拟 CPU 内存的索引。每次将某些内容压入堆栈时,堆栈指针都会减少该内容的大小,并且该内容会存储在减量后地址的模拟内存中。每当您从堆栈中弹出某些内容时,该值都会从存储在堆栈指针中的地址中获取,然后堆栈指针会增加该内容的大小。这就是 CPU 堆栈在许多不同 CPU 中的工作方式。

      如果您要实现 CPU 仿真器或 CPU 指令仿真器/解释器,则不太关心变量。您关心的是操作 CPU 寄存器和内存的 CPU 指令,因为您的程序是用 CPU 指令表示的。它们(指令)必须跟踪存储在堆栈中的所有本地变量,即它们相对于堆栈指针当前值的位置。

      例如,如果您考虑一个简单的子程序,它将两个传递给它的 16 位整数值添加到堆栈上,它可能看起来像这样,例如16 位 x86 汇编:

      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
      

      调用者可能看起来像这样:

          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
      

      【讨论】:

        【解决方案3】:

        'google 说它们是在 Stack 上分配的'

        这就是它在真实计算机中的实现方式,但这还不是全部。

        如果你想要一个虚拟解释器,你需要使用一个名为“哈希表”的数据结构。

        这是一个家庭作业问题。所以没有直接的答案: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);
            }
        }
        

        【讨论】:

        • 我知道 Hashtables,但我们不能为此使用高级构造,我假设汇编程序识别具有一些 id(数字)的变量还是我错了?
        • 不,你不是。但是如果你最终分配了“id”,你最终会得到一个使用两个数组完成的哈希表实现。使用 id 的两种方法: 1) 将变量压入堆栈,将其位置 (ID) 存储在一个数组中,并在另一个数组的相应位置存储变量名称。然后当您的堆栈更新时更新这些位置。这不是最终使用低级构造实现的哈希表吗?第二种方法是向堆栈数据结构添加一些额外的功能,允许您遍历其内容并搜索您想要的内容。
        【解决方案4】:

        答案是:视情况而定。作为语言设计者,您应该定义什么是可见性(如果定义了变量名,那么该名称在源代码的哪一部分是可用的?)和隐藏 (如果在另一个对象的可见区域中定义了另一个同名的对象,则哪个名称获胜?)变量规则。不同的语言有不同的规则,只比较Javascript和C++。

        所以,我会这样做。 (1) 引入namespace 的概念:在源文件的某个点可见的名称列表。 (请注意,这与 C++ 的命名空间概念不同。)命名空间应该能够将名称解析为某个适当的对象。 (2) 当您的解释器从一个过程更改为另一个过程、从一个文件更改为另一个文件、从一个块更改为另一个块、看到声明或块结束等时,实施更改命名空间的规则。

        这些步骤基本上对大多数语言都有效,而不仅仅是汇编程序。

        (我认为,谷歌提到的“堆栈分配”是指在一个单独的子程序中处理每个子程序的想法,并在本地重新定义一个命名空间,因此“在堆栈上”,所以它会在程序时自动弹出完成。)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-04-24
          • 2013-12-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多