表示具有联合和位字段的寄存器的问题

问题描述 投票:0回答:1

我正在用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中的位模式没有反映位字段(或我放置的位字段)表示的模式将它们放入结构体中)。我知道编译器可以在“分配单元”中打包位字段,甚至可以插入填充,但是为什么当我更改位字段的类型时行为会改变?

有人可以解释为什么吗?

c++ struct union cpu-registers bit-fields
1个回答
0
投票

您用于位字段的类型是内部用于存储的类型。实际布局完全由实现定义。我想您的编译器将位字段打包到存储单元中(在“坏”示例中为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 /平凡/标准布局)。


0
投票

uint8_t中有8位。结构中的前两个字段coarseXcoarseY各自具有5位,不能连续装入内存中的同一字节。编译器将coarseX存储在第一个字节中,然后必须将coarseY推入内存中的下一个字节,从而在内存中保留了不需要的3个未使用的位。

[接下来的3个字段coarseYbaseNametableAddressXbaseNametableAddressY共7位,因此它们适合于该第二个字节。

但是该字节不能容纳fineYunused,因此它们被推到内存中的下一个字节,在内存中留下了您不需要的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)。

© www.soinside.com 2019 - 2024. All rights reserved.