2014-09-12 28 views
8

Sto scrivendo un emulatore di microprocessore in C++ e uno dei miei obiettivi era rendere il codice molto leggibile. Per implementare i codici operativi, ho una struttura che sto usando per rappresentare le singole istruzioni del processore e contiene sia l'opcode che la distanza per far avanzare il contatore del programma. L'idea era di raggruppare le informazioni relative a ciascuna istruzione.C++: struct member in a switch statement

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}; 

mio piano originale era quello di definire tutti i codici operativi che utilizzano questa struct, come ho avuto l'impressione che const è preferito utilizzare #define per le costanti C++. Inoltre, sarei in grado di raggruppare in modo pulito tutti gli attributi correlati di un opcode.

Tuttavia, sembra che questo non funzioni per le istruzioni switch, come inizialmente previsto. Il seguente codice non verrà compilato e Visual Studio restituisce l'errore "espressione del caso non costante".

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; 
    } 

ho scaricato anche la più recente di Visual Studio compilatore (MSVC novembre 2013 CTP) per cercare di sfruttare constexpr da C++ 11, ma ho avuto lo stesso problema, e non si compila. Qui ho convertito la mia struttura in una classe e ho tentato di sfruttare constexpr, per garantire che i membri di un instruction potessero essere usati come costanti in fase di compilazione.

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); 

Io non sono davvero sicuro di cosa fare a questo punto, dal momento che sembra che il compilatore non capisce che i valori struct vengono assegnati come costanti.

Quindi esiste un modo per utilizzare un membro struct in un'istruzione switch o devo semplicemente passare a utilizzare #define? In alternativa, c'è un modo migliore per farlo mantenendo ancora un'organizzazione? Gradirei davvero qualsiasi aiuto o intuizione che tu possa offrire, grazie!

EDIT: dispiace, avrei fatto più chiaro che next_instruction è solo un int, non un instruction struct/oggetto

+0

La tua versione 'constexpr' dovrebbe funzionare. Quindi potrebbe essere che il tuo compilatore non lo implementa correttamente. Ma dovresti comunque mostrare la definizione di next_instruction. – juanchopanza

+0

'constexpr' è implementato solo in Visual Studio Next (14), Visual Studio 2013 non lo implementa. Inoltre, la versione più recente di VS è l'aggiornamento 3 del 2013, non il CTP di novembre. – Drop

+0

Inoltre, il tuo codice profuma di "Type Switch antipattern". Probabilmente potresti usare il polimorfismo qui (dinamico o statico) invece dei condizionali: [Metodi per eliminare lo switch in codice] (http://stackoverflow.com/questions/126409/ways-to-eliminate-switch-in-code). La maggior parte delle volte potrebbe essere ancora più veloce (dovrai profilare il profilo). – Drop

risposta

5

Se wan't per ottenere avventuroso con i modelli, una possibile soluzione senza il temuto la macro potrebbe essere la seguente.

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: Perché hai intenzione di creare oggetti non necessari? – Abhijit

10

Ho testato il codice in Qt Creator 3.1.2 con il compilatore MinGW 4.8.3. Basta sostituire const da constexpr in ogni definizione dell'istruzione ha reso il compilatore felice:

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; 
     } 
} 

Modifica per aggiungere alcune citazioni:

Il C++ Programming Language (fourh Edition) dice di etichette in istruzioni switch:

l'espressione nel caso etichette deve essere un'espressione costante di tipo integrale o di enumerazione. "(9.4.2 Cambia istruzioni").

Dalla sezione 10.4 espressioni costanti:

C++ offre due significato correlato di “costante”:

  • constexpr: Valutare al momento della compilazione
  • const: Non modificare in questo ambito

Fondamentalmente , il ruolo di constexpr è abilitare e assicurare valutazione in fase di compilazione, mentre il ruolo principale di const è specificare l'immutabilità nelle interfacce.

[...]

10.4.2 di const nelle espressioni costanti

[...] Un const inizializzato con un'espressione costante può essere utilizzato in un espressione costante. Un const differisce da un constexpr in quanto può essere inizializzato da da qualcosa che non è un'espressione costante; in quel caso , il const non può essere usato come espressione costante.

Le etichette nelle istruzioni switch richiedono constexpr in modo che la valutazione venga eseguita in fase di compilazione. Quindi sembra che const instruction HALT {0x76,1} non garantisca la valutazione del tempo di compilazione mentre lo fa constexpr instruction HALT {0x076,1}.

2

Tornando indietro dagli alberi per un momento, è una scommessa abbastanza sicura che stai scrivendo un emulatore 8080/Z80. Quindi non usare un interruttore. Disporre le strutture delle istruzioni in un array e utilizzare l'opcode da eseguire come indice.

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. 
}; 

Ora il codice diventa solo

opcodes[next_instruction].emulator(parameter list); 

Hai la possibilità di una scartando il codice operativo, o fare un controllo preliminare per garantire che ogni codice operativo è nel posto giusto nella tabella.

Questo ha anche il vantaggio che impedirà al codice di essere una routine monolitica suddividendolo in una routine per codice operativo. Se si sta scrivendo un emulatore Z80 questo diventerà un problema importante a causa dei gruppi 0xCB, 0xDD, 0xED e 0xFD, che in uno schema di commutazione richiederebbero un secondo switch all'interno di ciascuno dei gestori di caso per quei quattro pseudoopodi.