2014-09-12 11 views
8

Piszę emulator mikroprocesora w C++, a jednym z moich celów było uczynienie go bardzo czytelnym. Aby zaimplementować opkodami, mam strukturę, której używam do reprezentowania poszczególnych instrukcji procesora, i zawiera zarówno kod operacji, jak i odległość do przekroczenia licznika programu. Pomysł polegał na grupowaniu powiązanych informacji o każdej instrukcji.C++: członek struktury w oświadczeniu przełącznika

struct instruction 
{ 
    const int opcode; // instruction opcode 
    const int op_size; // how far to advance Program Counter 
}; 

const instruction HALT{0x76, 1}; 
const instruction NOP {0x00, 1}; 

Mój pierwotny plan był, aby zdefiniować wszystkie opcodes pomocą tej struktury, jak byłem pod wrażeniem, że const było preferowane do korzystania #define dla stałych C++. Ponadto byłbym w stanie uporządkować wszystkie powiązane atrybuty opcode.

Wydaje się jednak, że to nie zadziała w przypadku instrukcji switch, jak pierwotnie zamierzałem. Poniższy kod nie zostanie skompilowany, a Visual Studio podaje błąd "case expression not constant".

switch (next_instruction) { // next_instruction is an int parsed from a file 
    case HALT.opcode: 
     // do stuff 
     break; 
    case NOP.opcode: 
     // do stuff 
     break; 
    default: 
     std::cout << "Unrecognized opcode" << std::endl; 
      break; 
    } 

Ja również pobrać najnowszą kompilator języka Visual Studio (MSVC listopada 2013 CTP), aby spróbować dźwigni constexpr od C++ 11, ale miałem ten sam problem, i nie będzie skompilować. Tutaj skonwertowałem swoją strukturę do klasy i próbowałem wykorzystać constexpr, aby zapewnić, że członkowie instruction mogą być używane jako stałe w czasie kompilacji.

class Instruction 
{ 
    public: 
    constexpr Instruction(int code, int size) : opcode(code), op_size(size) {} 
    const int opcode; // instruction opcode 
    const int op_size; // how far to advance Program Counter 
}; 

constexpr Instruction HALT(0x76, 1); 
constexpr Instruction NOP (0x00, 1); 

Nie jestem pewien, co zrobić w tym momencie, ponieważ wydaje się, że kompilator nie rozumie, że wartości struct są przypisane jako stałe.

Czy istnieje jakiś sposób użycia elementu struct w instrukcji switch, czy powinienem po prostu przełączyć się na używanie #define? Alternatywnie, czy istnieje lepszy sposób, aby to zrobić, zachowując jednocześnie pewną organizację? Naprawdę doceniam każdą pomoc lub wgląd, jaki możesz zaoferować, dziękuję!

EDIT: Przepraszam, powinny były to jasne, że next_instruction jest tylko int, a nie instruction struct/przedmiot

+0

Twoja wersja 'constexpr' powinna działać. Możliwe, że Twój kompilator nie implementuje tego poprawnie. Ale i tak powinieneś pokazać definicję "next_instruction". – juanchopanza

+0

'constexpr' jest implementowany tylko w Visual Studio Next (14), Visual Studio 2013 go nie implementuje. Ostatnia wersja VS to 2013 aktualizacja 3, a nie listopadowe CTP. – Drop

+0

Twój kod również pachnie "Antypatternem typu przełącznika". Prawdopodobnie możesz użyć polimorfizmu tutaj (dynamiczny lub statyczny) zamiast warunków warunkowych: [Sposoby na wyeliminowanie zmiany w kodzie] (http://stackoverflow.com/questions/126409/ways-to-eliminate-switch-incode). Najczęściej może być jeszcze szybszy (musisz go profilować). – Drop

Odpowiedz

5

Jeśli wa dostać ryzykowny z szablonami, możliwe rozwiązanie bez bał makro może wyglądać następująco.

template<int code, int size> 
struct InstructionType 
{ 
    static const int opcode = code ; 
    static const int op_size = size; 
}; 
struct Instruction 
{ 
    int opcode; 
    int op_size; 
}; 
typedef InstructionType<0x76, 1> HALT; 
typedef InstructionType<0x00, 1> NOP; 


int main() 
{ 
    Instruction next_instruction; 
    switch (next_instruction.opcode) { 
    case HALT::opcode: 
     // do stuff 
     break; 
    case NOP::opcode: 
     // do stuff 
     break; 
    default: 
     std::cout << "Unrecognized opcode" << std::endl; 
     break; 
    } 
} 
+0

@Rimas: Dlaczego zamierzasz tworzyć niepotrzebne obiekty? – Abhijit

10

Testowałem twój kod w Qt Creator 3.1.2 z kompilatorem MinGW 4.8.3. Wystarczy zastępując const przez constexpr w każdej definicji instrukcji wykonany kompilator szczęśliwa:

struct instruction 
{ 
    const int opcode; // instruction opcode 
    const int op_size; // how far to advance Program Counter 
}; 

// Replacing "const" by "constexpr" int these two lines 
constexpr instruction HALT{0x76, 1}; 
constexpr instruction NOP {0x00, 1}; 

int main() { 
    int next_instruction = 0x76; 
    switch (next_instruction) { // next_instruction is an int parsed from a file 
     case HALT.opcode: 
      // do stuff 
      break; 
     case NOP.opcode: 
      // do stuff 
      break; 
     default: 
      std::cout << "Unrecognized opcode" << std::endl; 
       break; 
     } 
} 

Edycja, aby dodać jakieś cytaty:

C++ Programming Language (Fourh Edition) mówi o etykietach w sprawozdaniach przełączników:

wyrażenie w przypadku etykiet musi być stałym wyrażeniem od typ całkowania lub wyliczenia. "(9.4.2 Instrukcje przełączników").

Z sekcji 10.4 Constant wyrażeń:

C++ oferuje dwa powiązane znaczenie „stały”:

  • constexpr: ocenić w czasie kompilacji
  • const: Nie należy modyfikować w tym zakresie

Zasadniczo , constexpr 'rola polega na włączeniu i oszacowaniu czasu kompilacji, mając na uwadze, że główną rolą stałej jest określenie niezmienności w interfejsach.

[...]

10.4.2 const jest w wyrażeniami

[...] const inicjowany stałej ekspresji mogą być stosowane w stałej ekspresji. A const różni się od constexpr tym, że może być zainicjowany przez coś, co nie jest wyrażeniem stałym; w tym przypadku const nie może być używane jako wyrażenie stałe.

Etykiety w instrukcjach przełączników wymagają constexpr, aby ocena była wykonywana w czasie kompilacji. Wygląda więc na to, że const instruction HALT {0x76,1} nie zapewnia oceny czasu kompilacji podczas wykonywania constexpr instruction HALT {0x076,1}.

2

Odsunięcie się od drzew na chwilę, to dość bezpieczny zakład, w którym piszesz emulator 8080/Z80. Więc nie używaj w ogóle przełącznika. Rozmieść struktury instrukcji w tablicy i użyj kodu operacyjnego do wykonania jako indeks.

struct instruction 
{ 
    const int opcode; // instruction opcode 
    const int op_size; // how far to advance Program Counter 
    const void (*emulator)(parameter list); // code for this opcode 
}; 

void illegal(parameter list) 
{ 
    std::cout << "Unrecognized opcode" << std::endl; 
} 

instruction opcodes[] = // assuming 8080 for now 
{ 
{0x00, 1, nop_emulator}, // NOP 
{0x01, 3, lxib_emulator}, // LXI B 
etc. 
{0x08, 1, illegal},  // 0x08 is invalid on the 8080 
etc. 
}; 

Teraz Twój kod właśnie staje

opcodes[next_instruction].emulator(parameter list); 

Masz do wyboru albo odrzucając opcodu lub robi precheck aby upewnić się, że każdy kod operacji znajduje się w odpowiednim miejscu w tabeli.

Ma to również tę zaletę, że zatrzyma kod z jednej monolitycznej rutyny, dzieląc go na jedną rutynę za kod operacyjny. Jeśli piszesz emulator Z80, stanie się to poważnym problemem ze względu na grupy 0xCB, 0xDD, 0xED i 0xFD, które we wzorcu przełącznika będą wymagały drugiego przełącznika w każdej z procedur obsługi spraw dla tych czterech pseudo-opkodów.