I am writing a microprocessor emulator in C++, and one of my goals was to make it the code very readable. To implement opcodes, I have a struct which I am using to represent individual processor instructions, and it contains both the opcode and how far to advance the program counter. The idea was to group related information about each instruction.
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};
My original plan was to define all the opcodes using this struct, as I was under the impression that const
was preferred to using #define
for C++ constants. In addition, I would be able to cleanly group all related attributes of an opcode.
However, it seems that this will not work for switch statements, as I originally intended. The following code will not compile, and Visual Studio gives the error "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;
}
I've also downloaded the latest Visual Studio compiler (MSVC November 2013 CTP) to try to leverage constexpr
from C++11, but I had the same problem, and it will not compile. Here I converted my struct to a class and attempted to leverage constexpr
, to ensure that the members of an instruction
could be used as compile-time constants.
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);
I'm not really sure what to do at this point, since it appears that the compiler does not understand that the struct values are assigned as constants.
So is there any way to use a struct member in a switch statement, or should I just switch to using #define
? Alternatively, is there a better way to do this while still retaining some organization? I'd really appreciate any help or insight you can offer, thanks!
EDIT: Sorry, I should have made it clearer that next_instruction is just an int, not an instruction
struct/object
A general syntax of how switch-case is implemented in a 'C' program is as follows: switch( expression ) { case value-1: Block-1; Break; case value-2: Block-2; Break; case value-n: Block-n; Break; default: Block-1; Break; } Statement-x; The expression can be integer expression or a character expression.
Member functions inside the structure: Structures in C cannot have member functions inside a structure but Structures in C++ can have member functions along with data members.
The value of the expressions in a switch-case statement must be an ordinal type i.e. integer, char, short, long, etc. Float and double are not allowed. The case statements and the default statement can occur in any order in the switch statement.
The simple answer is No. You cant use it like that.
I've tested your code in Qt Creator 3.1.2 with MinGW 4.8.3 compiler. Just replacing const by constexpr in each instruction definition made the compiler happy:
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;
}
}
Edit to add some quotes:
The C++ Programming Language (Fourh Edition) says about labels in switch statements:
“The expression in the case labels must be a constant expression of integral or enumeration type.” (9.4.2 switch Statements”).
From Section 10.4 Constant Expressions:
C++ offers two related meaning of “constant”:
- constexpr: Evaluate at compile time
- const: Do not modify in this scope
Basically, constexpr’s role is to enable and ensure compile-time evaluation, whereas const’s primary role is to specify immutability in interfaces.
[…]
10.4.2 const’s in Constant Expressions
[…] A const initialized with a constant expression can be used in a constant expression. A const differs from a constexpr in that it can be initialized by something that is not a constant expression; in that case, the const cannot be used as a constant expression.
Labels in switch statements requieres constexpr so that evaluation is done at compile time.
So it seems that const instruction HALT {0x76,1}
does not ensure compile time evaluation while constexpr instruction HALT {0x076,1}
does.
If you wan't to get adventurous with templates, a possible solution without the dreaded macro could be as follows.
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;
}
}
Stepping back from the trees for just a moment, it's a fairly safe bet you're writing an 8080 / Z80 emulator. So don't use a switch at all. Arrange your instruction structures in an array, and use the opcode to be executed as an index.
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.
};
Now your code just becomes
opcodes[next_instruction].emulator(parameter list);
You have the option of either discarding the opcode, or doing a precheck to ensure that every opcode is in the correct place in the table.
This also has the advantage that it'll stop your code from being one monolithic routine by breaking it up into one routine per opcode. If you're writing a Z80 emulator this will become a major concern because of the 0xCB, 0xDD, 0xED and 0xFD groups, which in a switch pattern would require a second switch within each of the the case handlers for those four pseudo-opcodes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With