我正在用C ++编写一个微处理器模拟器,我的目标之一是使代码非常易读。为了实现操作码,我有一个结构,我用它来表示单独的处理器指令,它包含操作码和程序计数器的推进程度。我们的想法是将有关每条指令的相关信息分组。
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年11月CTP)以尝试利用C ++ 11中的constexpr
,但我遇到了同样的问题,并且无法编译。在这里,我将结构转换为类并尝试利用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值被指定为常量。
那么有没有办法在switch语句中使用struct成员,或者我应该切换到使用#define
?或者,有没有更好的方法来做到这一点,同时仍然保留一些组织?我非常感谢您提供的任何帮助或见解,谢谢!
编辑:抱歉,我应该更清楚地说明next_instruction只是一个int,而不是instruction
struct / object
答案 0 :(得分:10)
我已经使用MinGW 4.8.3编译器在Qt Creator 3.1.2中测试了您的代码。 在每个指令定义中只需用constexpr替换const就可以让编译器满意了:
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 ++编程语言(Fourh Edition)讲述了switch语句中的标签:
“案例标签中的表达式必须是常量表达式 积分或枚举类型。“(9.4.2开关语句”)。
从第10.4节“常量表达式”:
C ++提供了“常量”的两个相关含义:
- constexpr:在编译时进行评估
- const:请勿在此范围内修改
基本上, constexpr 的作用是启用确保 编译时评估,而const的主要作用是指定 接口的不变性。
[...]
10.4.2常量表达式中的常量
[...]用常量表达式初始化的const可用于a 不断表达。 const与constexpr的不同之处在于它可以 由不是常数表达式的东西初始化;在那里面 case,const不能用作常量表达式。
switch语句中的标签需要constexpr,以便在编译时完成评估。
因此,const instruction HALT {0x76,1}
似乎constexpr instruction HALT {0x076,1}
无法确保编译时评估。
答案 1 :(得分:5)
如果您不想使用模板进行冒险,那么没有可怕宏的可能解决方案可能如下所示。
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;
}
}
答案 2 :(得分: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组,这将成为一个主要问题,在开关模式中,在每个案例处理程序中需要第二个开关用于这四个伪操作码。