c++ - 表示带有 union 和位域的寄存器的问题

标签 c++ struct union cpu-registers bit-fields

我正在用 C++ 编写一个 NES 模拟器,我遇到了一个使用位字段来表示寄存器的问题,这导致了一个非常讨厌的错误。我将内部地址寄存器表示为:

union
    {
        struct
        {
            uint16_t coarseX : 5;            // bit field type is uint16_t, same as reg type
            uint16_t coarseY : 5;
            uint16_t baseNametableAddressX : 1;
            uint16_t baseNametableAddressY : 1;
            uint16_t fineY : 3;
            uint16_t unused : 1;
        } bits;
        uint16_t reg;
    } addressT, addressV;   // temporary VRAM adddress register and VRAM address register

所以我可以访问单个位域和整个寄存器。

最初我将寄存器写为:
union
    {
        struct
        {
            uint8_t coarseX : 5;             // bit field type is uint8_t, reg type is uint16_t
            uint8_t coarseY : 5;
            uint8_t baseNametableAddressX : 1;
            uint8_t baseNametableAddressY : 1;
            uint8_t fineY : 3;
            uint8_t unused : 1;
        } bits;
        uint16_t reg;
    } addressT, addressV;   // temporary VRAM adddress register and VRAM address register

当位字段的类型(例如粗略X)与寄存器(reg)的类型不同时,该错误是由位字段行为引起的。
在这种情况下,当我增加一个字段(即粗略X++)时,reg 成员被“错误地”更新,这意味着 reg 内的位模式没有反射(reflect)位字段(或我铺设的位字段)表示的模式在结构中取出它们)。
我知道编译器可以在“分配单元”中打包位字段,甚至可以插入填充,但是为什么当我更改位字段的类型时行为会发生变化?

有人可以解释为什么吗?

最佳答案

你自己说的:

I know that the compiler can pack bit fields inside "allocation units", and may even insert padding, ...



这正是正在发生的事情。
uint8_t里面有 8 位。结构中的第一个两个字段 coarseXcoarseY ,每个有 5 位,不能在内存中的单个字节内连续放置。编译器存储 coarseX在第一个字节中,然后必须推 coarseY到内存中的第 2 个字节,在 coarseX 之间的内存中留下 3 个未使用的位和 coarseY这抵消了您在寄存器中的值。

接下来的 3 个字段,coarseY , baseNametableAddressXbaseNametableAddressY ,总共 7 位,所以它们适合第二个字节。

但是那个字节不能容纳 fineYunused字段,因此它们被推送到内存中的第三个字节,在 baseNametableAddressY 之间的内存中留下 1 个未使用的位和 fineY这抵消了您在寄存器中的值。并且寄存器不能访问第三个字节!

所以,实际上,您的 struct最终表现得好像你已经这样声明了它:

    union
    {
        struct
        {
            // byte 1
            uint8_t coarseX : 5;
            uint8_t padding1 : 3;

            // byte 2
            uint8_t coarseY : 5;
            uint8_t baseNametableAddressX : 1;
            uint8_t baseNametableAddressY : 1;
            uint8_t padding2 : 1;

            // byte 3!
            uint8_t fineY : 3;
            uint8_t unused : 1;
            uint8_t padding3 : 4;
        } bits;
        struct {
            uint16_t reg; // <-- 2 bytes!
            uint8_t padding4; // <-- ! 
        }
    } addressT, addressV;   // temporary 

使用 uint16_t而不是 uint8_t ,您不会遇到添加额外付费的问题,因为为寄存器分配了足够的位来保存您定义的所有位。

关于c++ - 表示带有 union 和位域的寄存器的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60677299/

相关文章:

c++ - NodeJS Nan C++ 将嵌套对象绑定(bind)到插件实例

c++ - 通过引用函数传递指针参数是什么意思?

c++ - 使用 __declspec(dllexport) 的符号导出问题

c - 结构 - 解释输出 :

php - 两个表并集的行数增加的临时列

mysql - 在mysql中合并数据bt表

c++ - 如何在 VSCode 中设置 CMake 构建配置?

Python - 在 Django 中使用结构解包

c - 读取文件并将其存储在结构中,以便可以在其他函数中使用

sql - Postgresql 按不同表中的多列排序