Basit Makine Dili simülatörü

Nov 02 2020

Bu, okuma, yazma, yükleme, saklama, toplama, çıkarma, çarpma, bölme, modül, dallanma, negatifse dallanma, sıfırsa dallanma gibi temel işlemleri gerçekleştiren basit bir makine dili simülatörüdür.

Girişler kullanıcıdan onaltılık olarak alınır, bellek arraymaksimum 1 kelime tutabilen yerleşik tamsayılar olarak simüle edilir .

Bir sözcük 4 basamaktan oluşur, ilk ikisi işlenen kodunu (sml komut kodu), son ikisi işleneni (bellekteki konum) temsil eder. Simülatör aynı zamanda dizgi değişmezlerini okur ve çıkarır.

İşte kod.

sabitler.h

constexpr unsigned read = 0xA; // Read a word(int) from the keyboard into a specific location in memory
constexpr unsigned write = 0xB; // Write a word(int) from a specific location in memory to the screen
constexpr unsigned read_str = 0xC; // Read a word(string) from the keyboard into a specific location in memory
constexpr unsigned write_str = 0xD; // Write a word(string) from a specific location in memory to the screen
constexpr unsigned load = 0x14; // Load a word from a specific location in memory to the accumulator
constexpr unsigned store = 0x15; // Store a word from the accumulator into a specific location in memory
constexpr unsigned add = 0x1E; /* Add a word from a specific location in memory to the word in the accumulator; store the 
                                 result in the accumulator */
constexpr unsigned subtract = 0x1F;
constexpr unsigned multiply = 0x20;
constexpr unsigned divide = 0x21;
constexpr unsigned modulo = 0x22;
constexpr unsigned branch = 0x28; // Branch to a specific location in the memory
constexpr unsigned branchneg = 0x29; // Branch if accumulator is negative
constexpr unsigned branchzero = 0x2A; // Branch if accumulator is zero
constexpr unsigned halt = 0x2B; // Halt the program when a task is completed
constexpr unsigned newline = 0x32; // Insert a new line
 
constexpr unsigned end = -0x1869F; // End the program execution
constexpr unsigned memory_size = 1000;
constexpr unsigned sml_debug = 0x2C; // SML debug

registers.h

int accumulator = 0; 
unsigned instruction_counter = 0;
unsigned instruction_register = 0;
unsigned operation_code = 0;
unsigned operand = 0;

sml.h

#include "constants.h"

void memory_dump( int memory[memory_size], const unsigned &mem_size, const int &acc, const unsigned &ins_reg, \
    const unsigned &ins_cnt, const unsigned &opr_code, const unsigned &opr ); 

void execute( int memory[memory_size], int &acc, unsigned &ins_reg, unsigned &ins_cnt, unsigned &opr_code, unsigned &opr ); // executes the statement in sequential manner

void evaluate( int memory[memory_size], int &acc, unsigned &ins_reg, unsigned &ins_cnt, unsigned &opr_code, unsigned &opr );

void display_welcome_message();

bool division_by_zero( int memory[ memory_size ], unsigned operand );

sml.cpp

#include <iostream>
#include <iomanip>
#include <string>
#include "sml.h"

int temp_cnt = 0;  // holds instruction_counter when performing branch operation
std::string temp_str; // holds the string before it is written into the memory
bool debug = false;

void memory_dump( int memory[memory_size], const unsigned &mem_size, const int &acc, const unsigned &ins_reg, \
    const unsigned &ins_cnt, const unsigned &opr_code, const unsigned &opr ) 
{
    std::cout << "\nREGISTERS:\n";

    std::cout << std::setw( 25 ) << std::left << std::setfill( ' ' ) << "accumulator" << std::showpos
        << std::setw( 5 ) << std::setfill( '0' ) << std::internal << acc << '\n';

    std::cout << std::setw( 28 ) << std::left << std::setfill( ' ' ) 
    << "instruction counter" << std::noshowpos <<  std::setfill( '0' ) 
    << std::right << std::setw( 2 ) << ins_cnt << '\n';

    std::cout << std::setw( 25 ) << std::left << std::setfill( ' ' ) 
    << "instruction register" << std::showpos << std::setw( 5 ) << std::setfill( '0' ) 
    << std::internal << ins_reg << '\n';

    std::cout << std::setw( 28 ) << std::left << std::setfill( ' ' ) 
    << "operation code" << std::noshowpos <<  std::setfill( '0' ) 
     << std::right << std::setw( 2 ) << opr_code << '\n';

    std::cout << std::setw( 28 ) << std::left << std::setfill( ' ' ) 
    << "operand" << std::noshowpos <<  std::setfill( '0' ) 
     << std::right << std::setw( 2 ) << opr << '\n';

    std::cout << "\n\nMEMORY:\n";
    std::cout << "  ";

    for( int i = 0; i != 10; ++i ) 
        std::cout << std::setw( 6 ) << std::setfill( ' ') << std::right << i;
    for( size_t i = 0; i != mem_size; ++i ) 
    {
        if( i % 10 == 0 )
            std::cout << "\n" << std::setw( 3 ) << std::setfill( ' ' ) << i << " ";
        std::cout << std::setw( 5 ) << std::setfill( '0' ) << std::showpos << std::internal << memory[ i ] << " ";
    }
    std::cout << std::endl;
}

void execute( int memory[memory_size], int &acc, unsigned &ins_reg, \
    unsigned &ins_cnt, unsigned &opr_code, unsigned &opr ) 
{
    int divisor; 
    while( memory[ ins_cnt ] != 0  ) 
    {
        ins_reg = memory[ ins_cnt++ ];

        if( ins_reg < 1000 ) divisor = 0x10; 
        else if( ins_reg >= 1000 && ins_reg < 10000 ) divisor =  0x100; 
        else if( ins_reg >= 10000 && ins_reg < 100000 ) divisor =  0x1000; 

        opr_code = ins_reg /  divisor;
        opr = ins_reg %  divisor ;

        if( opr_code == halt )
            break;
        evaluate( memory, acc, ins_reg, ins_cnt, opr_code, opr );
        if( debug )
            memory_dump( memory, memory_size, acc, ins_reg, ins_cnt, \
        opr_code, opr );
    }
}

void evaluate( int memory[memory_size], int &acc, unsigned &ins_reg, \
    unsigned &ins_cnt, unsigned &opr_code, unsigned &opr ) 
{
    switch ( opr_code ) 
    {
        case read:
            std::cin >> memory[ opr ];
            break;
        case read_str:
            std::cin >> temp_str;
            memory[ opr ] = temp_str.size();
            for( int i = 1; i != temp_str.size() + 1; ++i )
                memory[ opr + i ] = int( temp_str[ i - 1 ] );
            break;
        case write:
            std::cout << memory[ opr ] << " ";
            break;
        case write_str:
            for( int i = 0; i != memory[ opr ] + 1; ++i ) {
                std::cout << char( memory[ opr + i ]);
            }
            break;
        case load:
            acc = memory[ opr ];
            break;
        case store:
            memory[ opr ] = acc;
            break;
        case add:
            acc +=  memory[ opr ];
            break;
        case subtract:
            acc -= memory[ opr ];
            break;
        case multiply:
            acc *= memory[ opr ];
            break;
        case divide:
            if ( division_by_zero( memory, opr ) ) 
            {
                memory_dump( memory, memory_size, acc, ins_reg, ins_cnt, opr_code, opr );
                exit( EXIT_FAILURE );      
            }
            else 
            {
                acc /= memory[ opr ];
                break;
            }
        case modulo:
            if( division_by_zero( memory, opr ) )
            {
                memory_dump( memory, memory_size, acc, ins_reg, ins_cnt, opr_code, opr );
                exit( EXIT_FAILURE );  
            }
            else
            {
                acc %= memory[ opr ];
                break;
            }
        case branch:
            temp_cnt = ins_cnt;
            ins_cnt = opr;
            execute( memory, acc, ins_reg, ins_cnt, opr_code, opr );
            ins_cnt = temp_cnt;  
            break;
        case branchneg:
            if( acc < 0 ) 
            {
                temp_cnt = ins_cnt;
                ins_cnt = opr;
                execute( memory, acc, ins_reg, ins_cnt, opr_code, opr );
                ins_cnt = temp_cnt;  
            }
            break;
        case branchzero:
            if( acc == 0 ) 
            {
                temp_cnt = ins_cnt;
                ins_cnt = opr;
                execute( memory, acc, ins_reg, ins_cnt, opr_code, opr );
                ins_cnt = temp_cnt;  
            }
            break;
        case newline:
            std::cout << '\n' << std::flush;
            break;
        case sml_debug:
            if ( opr == 1 ) debug = true;
            else if ( opr == 0 ) debug = false;
            else 
            {
                std::cout <<  std::setw( 5 ) << std::setfill( ' ') << std::left << "***" 
                << "Invalid debug mode" 
                << std::setw( 5 ) << std::right << "***\n";
            }
            break;
        default:
            break;
    }
}

void display_welcome_message () {
    std::cout << "***" <<  " WELCOME TO SIMPLETRON! " << "***\n\n";
    std::cout <<  std::setw( 5 ) << std::left << "***" 
        << "Please enter your program one instruction" 
        << std::setw( 5 ) << std::right << "***\n";

    std::cout << std::setw( 5 ) << std::left  << "***" 
    << "(or data word) at a time. I will type the" 
    << std::setw( 5 ) << std::right << "***\n"; 

    std::cout << std::setw( 5 ) << std::left << "***" 
    << "location number and a question mark (?)."  
    << std::setw( 6 ) << std::right << "***\n";

    std::cout << std::setw( 5 )  << std::left << "***"
    << "You then type the word for that location"  
    << std::setw( 6 ) << std::right  << "***\n";

    std::cout << std::setw( 5 )  << std::left << "***" 
    << "Type the sentinel -0x1869F to stop entering"  
    << std::setw( 5 ) << std::right << "***\n";

    std::cout << std::setw( 5 )  << std::left << "***" 
    << "your program"  
    << std::setw( 5 ) << std::right << "***";

    std::cout << "\n\n" << std::flush;
}

bool division_by_zero( int memory[ memory_size ], unsigned operand )
{
    if ( memory[ operand ] == 0 ) 
        {
            std::cout <<  std::setw( 5 ) << std::left << "***" 
                << "Attempting division by zero" 
                << std::setw( 5 ) << std::right << "***\n";
            std::cout <<  std::setw( 5 ) << std::left << "***" 
                << "Program terminated abnormally" 
                << std::setw( 5 ) << std::right << "***\n";
            std::cout << "\n";    
            return true;
        }
   return false;
}

main.cpp

#include <iostream>
#include <iomanip>
#include "registers.h"
#include "sml.h"

int main() 
{
    int memory[ memory_size ]{};
    size_t memory_size = sizeof( memory )/ sizeof( memory[ 0 ] );
    int temp;

    display_welcome_message();

    while( instruction_counter != memory_size ) 
    {
        std::cout << std::setw( 2 ) << std::setfill( '0' )
            << instruction_counter << " ? ";
        std::cin >> std::hex >> temp;
        if( temp == end ) {
            break;
        }   
        if( temp >= -0xB3E8 && temp < 0xB3E8 )
            memory[ instruction_counter++ ] = temp;
        else 
            continue;
    }

    instruction_counter = 0;
    std::cout << std::setfill( ' ' );
    std::cout <<  std::setw( 5 ) << std::left << "***" 
        << "Program loaded into memory" 
        << std::setw( 5 ) << std::right << "***\n";
    
    std::cout <<  std::setw( 5 ) << std::left << "***" 
        << "Program execution starts..." 
        << std::setw( 5 ) << std::right << "***\n";

    execute( memory, accumulator, instruction_register, instruction_counter, operation_code, operand );
    std::cout << std::endl;
}

Yanıtlar

8 pacmaninbw Nov 02 2020 at 05:51

Genel gözlemler

Bu tür bir problemin çözülmesi her zaman ilginçtir.

4. sorundan beri derslerden kaçıyorsun. C ++ sınıflarında, nesne yönelimli programlamaya giriş yolunuzdur ve sınıf harika araçlar sağlar. @ G.Sliepen'in incelemesinde belirttiği gibi, simülatör bir sınıf olsaydı çok daha iyi olurdu. Simülatör bir sınıf olarak uygulanmış olsaydı, global değişkenlere ihtiyaç olmazdı. Kamu arayüzleri execute(), evaluate()ve memory_dump()hafıza dizisi beri çok daha basit olacak ve kayıtları özel değişkenler olacaktır ve işlevi içine geçmesine gerek kalmaz.

Programı daha kolay hale getirmek için, kullanıcının simülatör programını değiştirmesine izin veren bir satır düzenleyici ekleyin. Bu şekilde, simülatör bellek dökerse programın çıkmasına gerek kalmaz. Simülatörün çalışması durabilir, kullanıcı hattı düzenleyebilir ve ardından simülasyonu yeniden başlatabilir. Programı bilinen bir duruma döndürmek yerine istisnaları kullanın exit(EXIT_FAILURE);.

Daha fazla bilgi için bu sorunun yanıtlarına bakmak isteyebilirsiniz .

Global Değişkenlerden Kaçının

Şu anda programda en az 8 global değişken bulunmaktadır registers.h:

int accumulator = 0;
unsigned instruction_counter = 0;
unsigned instruction_register = 0;
unsigned operation_code = 0;
unsigned operand = 0;

sml.cpp'de:

int temp_cnt = 0;  // holds instruction_counter when performing branch operation
std::string temp_str; // holds the string before it is written into the memory
bool debug = false;

Global değişkenler kullanan programları okumak, yazmak, hata ayıklamak ve sürdürmek çok zordur. Global değişkenler, program içindeki herhangi bir işlev tarafından değiştirilebilir ve bu nedenle, kodda değişiklik yapmadan önce her bir işlevin incelenmesini gerektirir. C ve C ++ 'da global değişkenler ad alanını etkiler ve birden çok dosyada tanımlanmışlarsa bağlantı hatalarına neden olabilirler. Bu yığın aşımı sorusundaki yanıtlar daha kapsamlı bir açıklama sağlar.

Simülatör bir sınıf olarak uygulandıysa, bu global değişkenlerin çoğu veya tümü özel değişkenler olabilir.

Kayıtlar, bir enun tarafından indekslenmiş bir dizi olarak uygulanabilir.

typedef enum
{
    ACCUMULATOR = 0,
    INSTRUCTION_COUNTER = 1,
    INSTRUCTION_REGISTER = 2,
    OPERATION_CODE = 3,
    OPERAND = 4,
    REGISTER_COUNT = 5
} REGISTERS;

    unsigned registers[static_cast<unsigned>(REGISTER_COUNT)];
    registers[ACCUMULATOR] = 0;

Sml.cpp'deki kod bir sınıfa dönüştürülmediyse, bu değişkenlerin her birini statickapsamı yalnızca sml.cpp dosyasının kendisinin kapsamı olacak şekilde yapmak daha iyi olur , şu anda diğer .cppdosyalardan erişilebilirler. gibi main.cpp.

Kayıtların global değişkenleri sml.cpp, programın diğer bölümleri için gerekli olmadığından bildirilmelidir main.cpp.

Muhafızları Dahil Et

C ++ 'da ve C programlama dilinde, kod içe aktarma mekanizması #include FILEaslında kodu derleyici tarafından oluşturulan geçici bir dosyaya kopyalar. Diğer bazı modern dillerin aksine C ++ (ve C) bir dosyayı birden çok kez içerecektir. Bu programcıların kullanmasını önlemek için 2 biçime sahip olabilen korumaları içerir:

daha taşınabilir biçim, kodu bir çift ön işlemci deyimine yerleştirmektir

#ifndef SYMBOL
#define SYMBOL
// All other necessary code
#endif // SYMBOL

A popular form that is supported by most but not all C++ compilers is to put #pragma once at the top of the header file.  

Bir dosyanın içeriğinin birden çok kez eklenmesini önlemek için yukarıdaki 2 yöntemden birini kullanmak, C ++ programlama için en iyi uygulamadır. Bu, dosya birden çok kez dahil edilirse derleme sürelerini iyileştirebilir, ayrıca derleyici hatalarını ve bağlayıcı hatalarını da önleyebilir.

Karmaşıklık

İşlev evaluate()çok karmaşıktır (çok fazla yapar) ve performans iyileştirilebilir. Tanımlanan işlem kodu değerleri sıralıysa constants.hve sıfırdan başlarsa, işlem kodlarının her birini uygulamak için bir işlev dizisi kullanılabilir. Daha sonra her işlem kodu, işlem kodu ile o diziye endekslemeyi basitleştirerek değerlendirilebilir. Bu, işlevdeki kod miktarını büyük ölçüde azaltacaktır. Daha hızlı çalışır çünkü bir diziye indeksleme, oluşturulan derleme kodunda birden çok if deyiminden daha hızlıdır. Bu aynı zamanda komut setini genişletmeyi de kolaylaştırır.

6 G.Sliepen Nov 02 2020 at 03:40

Rakamlar ve kelime boyutları

Kodunuzda şöyle bir kod görüyorum:

constexpr unsigned read = 0xA;
int accumulator = 0;

Bu int, kodunuzu derlemekte olduğunuz makinedeki bir kelimenin boyutu ne olursa olsun, kelime boyutlarını bağladığınız anlamına gelir . Simüle edilmiş makinenizdeki işaretli ve işaretsiz bir kelimenin boyutunu açıkça tanımlayan yeni tür takma adları oluşturabilirim:

using sword = int32_t;
using uword = uint32_t;

Sonra rakamlar var. Kullanıcıdan gelen girişin onaltılık olduğunu, kelimelerin 4 rakam olduğunu söylüyorsunuz, ancak koddan bunlar ondalık rakamlar gibi görünüyor? Bu pek tutarlı değil. Çoğu bilgisayar ikinin gücüyle çalışır ve bu da simülasyonu çok daha hızlı hale getirir (bölme ve modulo işlemleri oldukça pahalıdır).

classMakinenin durumunu tutmak için bir oluşturun

Her seferinde çok sayıda değişken geçirmeniz gereken çok sayıda sınıf dışı fonksiyona sahip olmak yerine class, simüle edilmiş makineyi temsil eden ve yazmaçlar ve bellek için üye değişkenler içeren bir oluşturmak daha mantıklıdır. yani:

class Machine {
    int accumulator = 0; 
    unsigned instruction_counter = 0;
    ...
    std::vector<int> memory(memory_size);

    void memory_dump();
    void evaluate();

public:
    void load_program();
    void execute();
};

Ayrıca tüm sabitleri içeriye taşıyabilirsiniz class Machine, böylece artık küresel isim alanını kirletmezler, özellikle de gibi isimleriniz readve writebu gölge POSIX işlevlerine sahip olduğunuzda.

Makinenin işleyişiyle ilgili olmadığı için muhtemelen sadece içeride olması gerekenler dışında her şeyi sml.cppiçine taşırdım.class Machinedisplay_welcome_message()main.cpp

Sihirli sabitlerden kaçının

-0xB3E8Ve dışındaki tüm sabitler için özel isimleriniz var 0xB3E8. Bunların nesi var? Bunlara da bir isim verin.

Biçimlendirme kitaplığı kullanmayı düşünün

iostreamC ++ 'daki işlevselliği kullanarak güzel biçimlendirilmiş çıktı oluşturmak çok can sıkıcıdır. Çok fazla kod gerektirir, kolayca hatalar yapılır ve kaynak kodu berbat görünür. C ++ 20'yi zaten kullanabiliyorsanız, kullanmaya başlamanızı şiddetle tavsiye ederim std::format(), ancak yapamıyorsanız , std::format()C ++ 'nın önceki sürümlerine dayanan ve onlarla çalışacak olan kitaplık olan fmtlib kullanmayı düşünün . Bu, kodunuzu şu şekilde yeniden yazabileceğiniz anlamına gelir:

std::cout << std::format("{:02} ? ", instruction_counter);
...
std::cout << std::format("{:*^40}\n", " Program loaded into memory ");
...
std::cout << std::format("{:*^40}\n", " Program execution starts... ");