用并集和位字段表示寄存器时出现问题 我在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
因此,我可以访问单个位字段,以及整个寄存器 最初,我将登记簿写为:用并集和位字段表示寄存器时出现问题 我在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,c++,struct,union,cpu-registers,bit-fields,C++,Struct,Union,Cpu Registers,Bit Fields,因此,我可以访问单个位字段,以及整个寄存器 最初,我将登记簿写为: union { struct { uint16_t coarseX : 5; // bit field type is uint16_t, same as reg type uint16_t coarseY : 5; uint16_t baseNametableAddressX : 1;
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
该错误是由位字段行为引起的,当位字段的类型(例如,ROSSERX)与寄存器reg的类型不同时。
在这种情况下,当我增加一个字段(即Groassex++)时,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位对于所有位字段都足够了,因此编译器可以按照您需要的方式对它们进行打包。有关更多信息,请参阅
还记得C++中的非活动联盟成员是UB。因此,最好使用一个uint16_t字段和访问器,这些字段和访问器不会阻止类型为POD/triple/standard layout。
用于位字段的类型是用于内部存储的类型。实际布局完全由实现定义。我假设编译器在“坏”示例中将位字段打包到存储单元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位对于所有位字段都足够了,因此编译器可以按照您需要的方式对它们进行打包。有关更多信息,请参阅
还记得C++中的非活动联盟成员是UB。因此,最好使用一个uint16_t字段和访问器,这些字段和访问器不会阻止类型为POD/普通/标准布局。
您自己说过: 我知道编译器可以在分配单元中打包位字段,甚至可以插入填充 这正是正在发生的事情 uint8\u t中有8位。结构中的前两个字段,即roarsex和roarsey,每个字段有5位,无法在内存中的单个字节内连续填充。编译器将粗X存储在第一个字节中,然后必须将粗Y推送到内存中的第二个字节,在粗X和粗Y之间的内存中保留3个未使用的位,以偏移寄存器中的值 接下来的3个字段,粗略、baseNametableAddressX和baseNametableAddressY,总共7位,因此它们适合于第2个字节 但该字节不能保存fineY和unused字段,因此它们被推送到内存中的第三个字节,在baseNametableAddressY和fineY之间的内存中留下1个未使用的位,用于偏移寄存器中的值。寄存器无法访问第三个字节 因此,实际上,您的结构最终会像这样声明: 协会 { 结构 { //字节1 uint8_t粗x:5; uint8_t填充1:3; //字节2 uint8粗度:5; uint8_t baseNametableAddressX:1; uint8_t baseNametableAddressY:1; uint8_t填充2:1; //字节3! uint8精细度:3; 未使用的单元:1; uint8_t填充3:4; }比特; 结构{ uint16\u t reg;//你自己说过: 我知道编译器可以在分配单元中打包位字段,甚至可以插入填充 这正是正在发生的事情 uint8_t中有8位。结构中的前两个字段,即粗X和粗Y,每个字段有5位,不能连续地放入内存中的单个字节中。编译器将粗X存储在第一个字节中,然后必须将粗Y推到内存中的第二个字节,在粗X和粗Y之间的内存中留下3个未使用的位,以偏移内存中的值登记册 接下来的3个字段,粗略、baseNametableAddressX和baseNametableAddressY,总共7位,因此它们适合于第2个字节 但是该字节不能保存fineY和unused字段,因此它们被推到内存中的第三个字节,在baseNametableAddressY和fineY之间的内存中留下1个未使用的位,用于偏移寄存器中的值。并且寄存器无法访问该第三个字节 因此,实际上,您的结构最终会像这样声明: 协会 { 结构 { //字节1 uint8_t粗x:5; uint8_t填充1:3; // 字节2 uint8粗度:5; uint8_t baseNametableAddressX:1; uint8_t baseNametableAddressY:1; uint8_t填充2:1; //字节3! uint8精细度:3; 未使用的单元:1; uint8_t填充3:4; }比特; 结构{uint16_t reg;//因此,对于将来,我建议不要使用位字段,而是写访问器,并使用位移位和位掩码。位字段填充取决于实现。您使用的编译器、编译器版本和编译器选项是什么?因此,对于将来,我建议不要使用位字段,而是写访问器,并使用位移位和位掩码。位字段添加取决于实现。您使用的编译器、编译器版本和编译器选项是什么?因此,概括一下,如果我使用uint8,分配单元是一个字节,并且由于实现不允许跨字节,它会插入填充。如果我使用uint16,分配单元可能是16位宽的无符号短,并且因为位字段是d在不跨越字节边界的情况下,它们被打包在一起。这就是Visual C++在我的情况下所做的,但是它可以从另一个实现中改变。我是正确的吗?如果我为每个位字段使用不同的数据类型,那会怎么样?@卢卡:是的,当你跨越分配单元之间的边界时,无论它们的大小是什么,都会得到填充。因此,如果我使用UIT88T,分配单元是一个字节,因为实现不允许跨字节之间插入填充。如果使用UTIN 16t,分配单元可能是16位宽的无符号短,并且由于位域不跨越字节边界,所以它们被打包到THOHTEL。这就是Visual C++在我的情况下所做的,但是我t可以从另一个实现中更改。我说得对吗?如果我对每个位字段使用不同的数据类型呢?@Luca是的,每当你跨越分配单元之间的边界时,不管它们的大小,你都会插入填充。