2014-09-12 3 views
8

Я пишу микропроцессорный эмулятор на C++, и одной из моих целей было сделать его очень читаемым. Чтобы реализовать коды операций, у меня есть структура, которую я использую для представления отдельных инструкций процессора, и она содержит как код операции, так и способ продвижения счетчика программ. Идея состояла в том, чтобы группировать соответствующую информацию о каждой инструкции.C++: член структуры в операторе switch

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

Мой первоначальный план был определить все опкоды, используя эту структуру, так как я был под впечатлением, что const предпочтение было отдано с использованием #define для констант C++. Кроме того, я смог бы сгруппировать все связанные атрибуты кода операции.

Однако, похоже, что это не будет работать для операторов switch, как я и предполагал. Следующий код не будет компилироваться, а Visual Studio дает ошибку «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; 
    } 

Я также скачал последнюю Visual Studio компилятор (MSVC ноября 2013 CTP), чтобы попытаться левериджа constexpr от C++ 11, но у меня была такая же проблема, и она не будет компилироваться. Здесь я преобразовал свою структуру в класс и попытался использовать constexpr, чтобы гарантировать, что члены instruction могут использоваться как константы времени компиляции.

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

Я не совсем уверен, что делать в этой точке, так как кажется, что компилятор не понимает, что значения Struct назначены как константы.

Так можно ли использовать элемент struct в инструкции switch, или я должен просто переключиться на использование #define? Альтернативно, есть ли лучший способ сделать это, сохраняя при этом некоторую организацию? Я бы очень признателен за любую помощь или прозрение, которое вы можете предложить, спасибо!

EDIT: К сожалению, я должен был бы сделать его более ясным, что next_instruction это просто INT, а не instruction структура/объект

+0

Ваша версия 'constexpr' должна работать. Поэтому может быть, что ваш компилятор не реализует это правильно. Но вы должны показать определение 'next_instruction' в любом случае. – juanchopanza

+0

'constexpr' реализован только в Visual Studio Next (14), Visual Studio 2013 не реализует его. Кроме того, последняя версия версии VS - обновленная версия 2013 года 3, а не ноябрьская CTP. – Drop

+0

Кроме того, ваш код пахнет «Type Switch antipattern». Вероятно, вы можете использовать здесь полиморфизм (динамический или статический) вместо условных: [Способы устранения коммутатора в коде] (http://stackoverflow.com/questions/126409/ways-to-eliminate-switch-in-code). В большинстве случаев это может быть еще быстрее (вам нужно будет профилировать его). – Drop

ответ

5

Если вы wan't получить авантюрным с шаблонами, возможное решение без страшился макрос может быть следующим.

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: Почему вы намереваетесь создавать ненужные объекты? – Abhijit

10

Я проверил ваш код в Qt Creator 3.1.2 с помощью компилятора MinGW 4.8.3. Просто заменив сопзЬ на constexpr в каждом определении инструкции сделал компилятор счастливым:

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

Изменить, чтобы добавить некоторые цитаты:

C++ Programming Language (Fourh Edition) говорит о ярлыках в операторах переключателя:

«выражение в случае метки должен быть постоянным выражением из интегрального или перечисляемого типа. "(9.4.2).

Из раздела 10.4 константные выражения:

C++ предлагает два связанных значения «постоянной»:

  • constexpr: Evaluate во время компиляции
  • Const: Не изменяйте в этой области

В основном , constexpr Роль должна быть включена и обеспечить оценка времени компиляции, в то время как основная роль const заключается в том, чтобы указать неизменяемость интерфейсов .

[...]

10.4.2 Уст-х в константах

[...] Константная инициализируется константным выражением может быть использовано в постоянном выражении. A const отличается от constexpr тем, что может быть инициализировано тем, что не является постоянным выражением; в этом случае случай, const не может использоваться как постоянное выражение.

Этикетки в операторах switch требуют constexpr, так что оценка выполняется во время компиляции. Так что кажется, что const instruction HALT {0x76,1} не обеспечивает оценку времени компиляции, а constexpr instruction HALT {0x076,1} делает.

2

Отступив назад от деревьев на мгновение, достаточно уверенно, что вы пишете эмулятор 8080/Z80. Поэтому не используйте переключатель вообще. Расположите свои структуры инструкций в массиве и используйте код операции, который будет выполняться как индекс.

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

Теперь ваш код просто становится

opcodes[next_instruction].emulator(parameter list); 

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

Это также имеет то преимущество, что это остановит ваш код как единую монолитную процедуру, разбив ее на одну процедуру для каждого кода операции. Если вы пишете эмулятор Z80, это станет серьезной проблемой из-за групп 0xCB, 0xDD, 0xED и 0xFD, которые в шаблоне коммутатора потребуют второго коммутатора в каждом из обработчиков case для этих четырех псевдокодов.

Смежные вопросы