我正在用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
该错误是由于位字段的类型(例如,randomX)与寄存器(reg)的类型不同时,由位字段行为引起的。在这种情况下,当我增加一个字段(即,randomX ++)时,reg成员被“错误地更新”,这意味着reg中的位模式没有反映位字段(或我放置的位字段)表示的模式将它们放入结构体中)。我知道编译器可以在“分配单元”中打包位字段,甚至可以插入填充,但是为什么当我更改位字段的类型时行为会改变?
有人可以解释为什么吗?
您用于位字段的类型是内部用于存储的类型。实际布局完全由实现定义。我想您的编译器将位字段打包到存储单元中(在“坏”示例中为uint8_t),但不允许它们跨越存储单元边界。喜欢:
uint8_t coarseX : 5;
// 3 bits remain (out of 8), not enough for coarseY. So these become padding,
// and next storage unit starts here
uint8_t coarseY : 5;
uint8_t baseNametableAddressX : 1;
uint8_t baseNametableAddressY : 1;
// 1 bit remain. Again, too little.
uint8_t fineY : 3;
uint8_t unused : 1;
在“好”示例中,对于所有位字段,16位就足够了,因此编译器可以按照您需要的方式打包它们。有关更多信息,请参见https://en.cppreference.com/w/cpp/language/bit_field。
还请记住,在C ++中,访问非活动的联合成员是UB。因此,最好使用单个uint16_t字段和访问器(这不会阻止类型为POD /平凡/标准布局)。
uint8_t
中有8位。结构中的前两个字段coarseX
和coarseY
各自具有5位,不能连续装入内存中的同一字节。编译器将coarseX
存储在第一个字节中,然后必须将coarseY
推入内存中的下一个字节,从而在内存中保留了不需要的3个未使用的位。
[接下来的3个字段coarseY
,baseNametableAddressX
和baseNametableAddressY
共7位,因此它们适合于该第二个字节。
但是该字节不能容纳fineY
和unused
,因此它们被推到内存中的下一个字节,在内存中留下了您不需要的1个未使用的位。
因此,有效地,您的struct
最终就像您这样声明它一样:
union
{
struct
{
uint8_t coarseX : 5;
uint8_t padding1 : 3;
uint8_t coarseY : 5;
uint8_t baseNametableAddressX : 1;
uint8_t baseNametableAddressY : 1;
uint8_t padding2 : 1;
uint8_t fineY : 3;
uint8_t unused : 1;
uint8_t padding3 : 4;
} bits;
uint16_t reg;
} addressT, addressV; // temporary
使用uint16_t
而不是`uint8_t,您不会遇到此问题,因为对于定义的所有15位,都有足够的可用位(16)。