2011-12-25 2 views
1

для моей домашней работы Мне нужно написать очень мало виртуального 16-битного Ассемблера-переводчика в C#. Имитирует ОЗУ с байтовым массивом (64k) и регистром с переменными (A, B, C, ...). Теперь мне нужен способ сохранить локальные переменные, google говорит, что они выделены на стеке.ASM Interpreter: как хранятся локальные переменные?

Но мне непонятно, когда они выделены на стеке (с помощью push ...), как Интерпретатор обращается к ним, когда они используются позже?

Смотрите следующие 2 строки:

pi INT 3 
mov A, pi 

В первой строке пи выделяется в стеке, во второй строке, используется пи, но как следует Интерпретатор знать, где пи в стеке получить доступ к своим данным? (Мой стек байт-массив тоже с 2-хелперов-функции (толчок, поп), есть также указатель на вершину стека)

ответ

0

Обычно нет отдельной памяти стека, вместо этого стек находится в обычной ОЗУ, поэтому у вас есть указатель стека, который отслеживает его.

Кроме того, как правило, локальные переменные выделяются в начале подпрограммы путем копирования указатель стека на другой регистр, а затем переместить указатель стека, чтобы освободить место для переменных:

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 
+0

спасибо, что я искал. –

0

«Google говорит, что они выделяются на стек»

вот как это реализовано на реальных компьютерах, но это не вся история.

Если вы хотите использовать виртуальный интерпретатор, вам необходимо использовать структуру данных, называемую «Хэш-таблица».

Ну, это вопрос о домашнем задании. Поэтому нет прямого ответа: P Но следующий код объяснит, как использовать таблицу хэшей. Сохраните имена переменных и значения в таблицах Hash.

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); 
    } 
} 
+0

Я знаю Hashtables, но мы не должны использовать высокие конструкции уровня для этого, я предполагаю, что ассемблер идентифицирует переменные некоторые идентификаторы (числа) или я ошибаюсь? –

+0

нет, вы не являетесь. Но если вы назначаете «идентификаторы», в итоге вы получите реализацию хэш-таблицы, выполненную с использованием двух массивов. Два способа использования идентификаторов: 1) Вставьте переменную в стек, сохраните ее позицию (ID) в одном массиве и в соответствующем месте в другом имени хранилища массива. Затем, когда ваши обновления стека обновляют эти местоположения. Разве это не хэш-таблица в конечном итоге реализована с использованием конструкций низкого уровня? Второй способ заключается в добавлении дополнительной функциональности в вашу структуру данных стека, которая позволяет выполнять итерацию содержимого и поиск того, что вы хотите. – Shaunak

0

Ответ: это зависит. Вы, как разработчик языка, должны определить, какова видимость (если определено имя переменной, в пределах какой части исходного кода указано имя?) И скрывается (если есть еще один объект с тем же имя, определенное в области видимости другого объекта, имя которого выигрывает?) правила переменных. Различные языки имеют разные правила, просто сравнивают Javascript и C++.

Итак, я сделал бы это так. (1) Ввести понятие namespace: список имен, видимых в определенной точке исходного файла. (Обратите внимание, что это не то же самое, что понятие пространства имен C++.) Пространство имен должно иметь возможность разрешить имя для некоторого подходящего объекта. (2) Внедрить правила для изменения пространств имен, когда ваш интерпретатор изменяется от одной процедуры к другой, от одного файла к другому, от одного блока к другому, видит объявление или конец блока и т. Д.

Эти шаги в основном действителен для большинства языков, а не только для ассемблера.

(Я думаю, ссылка Google на «выделение на стек» относится к идее обработки каждой подпрограммы в отдельной подпрограмме и переопределению пространства имен там локально, поэтому «на стеке», поэтому она будет автоматически выскользнуть, когда процедура заканчивается.)

1

Как правило, данные стека доступны относительно через stack pointer, который представляет собой регистр процессора, что указывает на последний элемент, хранящийся в стеке. Вы можете думать об этом как о индексе в память эмулируемого процессора. Каждый раз, когда вы толкаете что-то в стек, указатель стека уменьшается на величину этого и что-то запоминается в эмулируемой памяти по адресу после декремента. Всякий раз, когда вы выталкиваете что-то из стека, значение берется из адреса, хранящегося в указателе стека, а затем указатель стека увеличивается с увеличением этого значения. Вот как работают стеки процессора во многих разных 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 
Смежные вопросы