作为论文的一部分,我正在研究C ++中的6502仿真器。它有6个寄存器,其中大多数仅保存值,但有一个特殊的-处理器状态。它是8位宽,每个位意味着一个不同的标志。对我来说,最好的选择似乎是使其成为std :: bitset <8>并创建一个对应的枚举类,以将其值映射到真实位,如下所示:
enum class PSFlags : uint8_t
{
Carry = 0,
Zero = 1,
InterruptDisable = 2,
Decimal = 3,
Break = 4,
Unknown = 5,
Overflow = 6,
Negative = 7
};
struct Registers
{
int8_t A;
int8_t X;
int8_t Y;
uint8_t SP;
uint16_t PC;
static constexpr uint8_t PSSize = 8;
std::bitset<PSSize> PS;
constexpr Registers() noexcept :
A(0),
X(0),
Y(0),
SP(0xFF),
PS(0b00100100),
PC(0)
{
}
};
现在,如果我要引用以下三种之一:PS的大小,标志号或我本身的位集:
Registers::PSSize; // size
PSFlags::Carry; // flag number
Registers r; r.PS; // bitset itself
每个呼叫以非常不同的方式访问值的地方。我想使其更加一致,例如
Registers::PS::value; // for the bitset itself
Registers::PS::size; // for the size
Registers::PS::flags::Carry; // for the name of flag
您对如何在不创建代码的疯狂或丑陋构造的情况下实现这种(或类似)一致性有任何好的想法?
OP想要的(或类似的东西可以使用嵌套的struct
来实现。
只是为了好玩,我试图为OP建模:
#include <bitset>
struct Registers
{
int8_t A;
int8_t X;
int8_t Y;
uint8_t SP;
static constexpr uint8_t PSSize = 8;
struct PS: std::bitset<PSSize> {
enum Flags {
Carry = 0,
Zero = 1,
InterruptDisable = 2,
Decimal = 3,
Break = 4,
Unknown = 5,
Overflow = 6,
Negative = 7
};
static constexpr unsigned Size = PSSize;
constexpr PS(std::uint8_t value):
std::bitset<PSSize>((unsigned long long)value)
{ }
std::uint8_t value() const { return (std::uint8_t)to_ulong(); }
} PS;
uint16_t PC;
constexpr Registers() noexcept :
A(0),
X(0),
Y(0),
SP(0xFF),
PS(0x24),//PS(0b00100100),
PC(0)
{
}
} r;
一个小测试来证明这一点:
#include <iomanip>
#include <iostream>
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__
int main()
{
std::cout << std::hex << std::setfill('0');
DEBUG(std::cout << Registers::PS::Flags::Carry << '\n');
DEBUG(std::cout << r.PS[Registers::PS::Flags::Carry] << '\n');
DEBUG(std::cout << Registers::PS::Flags::InterruptDisable << '\n');
DEBUG(std::cout << r.PS[Registers::PS::Flags::InterruptDisable] << '\n');
DEBUG(std::cout << Registers::PS::Flags::Break << '\n');
DEBUG(std::cout << r.PS[Registers::PS::Flags::Break] << '\n');
DEBUG(std::cout << Registers::PS::Size << '\n');
DEBUG(std::cout << "0x" << std::setw(2) << (unsigned)r.PS.value() << '\n');
// done
return 0;
}
输出:
std::cout << Registers::PS::Flags::Carry << '\n';
0
std::cout << r.PS[Registers::PS::Flags::Carry] << '\n';
0
std::cout << Registers::PS::Flags::InterruptDisable << '\n';
2
std::cout << r.PS[Registers::PS::Flags::InterruptDisable] << '\n';
1
std::cout << Registers::PS::Flags::Break << '\n';
4
std::cout << r.PS[Registers::PS::Flags::Break] << '\n';
0
std::cout << Registers::PS::Size << '\n';
8
std::cout << "0x" << std::setw(2) << (unsigned)r.PS.value() << '\n';
0x24
注意:
关于命名嵌套结构Registers::PS
和具有相同名称的成员Registers::PS
是我期望的工作。不过,通常我将大写的起始字符用于类型标识符,将小写的起始字符用于变量。因此,我通常不会遇到这个问题。
由于对此有疑问,我针对各种编译器测试了struct Registers
(尽管我不会将其作为对标准的证明):Compiler Explorer
std::
容器的派生应谨慎(即最好不要这样做)。可能出于性能原因,std::
容器都不提供具有相应后果的virtual
析构函数。在上面的代码中,这应该没有问题。
[6502让我想起了我第一次尝试的Commodore 64(尽管C64具有更现代的6510 CPU])。但是,这很久以前...;-)