Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ size of structure with bit fields

Tags:

c++

gcc

arm

I have the following three unions:

typedef union {
    struct {
        uint16_t : 2;
        uint16_t numberOfWords : 10;
        uint16_t  : 4;
        uint16_t dataFormat : 8;
        uint16_t : 8;
    } bf;
    uint32_t dw;
} HeaderT;

typedef union {
    struct {
        uint16_t : 4;
        uint16_t lsb : 8;
        uint16_t  : 4;
        uint16_t msb : 8;
        uint16_t : 8;
    } bf;
    uint32_t dw;
} RegisterT;

typedef union {
    struct {
        uint16_t : 2;
        uint16_t lsb : 10;
        uint16_t : 2;
        uint16_t msb : 10;
        uint16_t : 8;
    } bf;
    uint32_t dw;
} BinT;

I get sizeof(HeaderT) == 4, sizeof(RegisterT) == 4, but sizeof(BinT) == 8! And I don't know why.

Doing

typedef union {
    struct {
        uint16_t : 2;
        uint16_t lsb : 10;
        uint16_t : 2;
        uint16_t msb : 10;
        uint16_t : 8;
    } bf __attribute__((packed));
    uint32_t dw;
} BinT;

didn't help. I need BinT to be 32-bits wide; it's memory-mapped to a bunch of registers on an FPGA.

Does anyone know what's going on? I'm using gcc on ARMv7. However, I'm seeing the same on my gcc on x86_64 VirtualBox VM.

Thanks.

like image 679
Damian Birchler Avatar asked Mar 13 '23 11:03

Damian Birchler


1 Answers

A bit field member cannot be split in two (or more) primitives. Well, as far as the standard is concerned, no packing is guaranteed at all. It's implementation defined. But this is a typical limitation, if the implementation does pack the bit fields.

Let's see what the GCC manual says about the implementation defined behaviour:

  • Whether a bit-field can straddle a storage-unit boundary (C90 6.5.2.1, C99 and C11 6.7.2.1).

Determined by ABI.

I'm not sure if this applies to ARM, but usually GCC conforms to the 64-bit Itanium spec.

(Assuming left-to-right packing) Your "bit distribution" between primitives is now:

uint16_t: 2 10 2 // 10 won't fit anymore
uint16_t: 10     // 8 won't fit anymore
uint16_t: 8

Those three uint16_t cannot possibly fit in 32 bits.

This:

union BinT {
    struct {
        uint32_t : 2;
        uint32_t lsb : 10;
        uint32_t : 2;
        uint32_t msb : 10;
        uint32_t : 8;
    } bf;
    uint32_t dw;
};

Should work since now all bit fields can share a single primitive.

OK, so by changing the uint16_t's throughout to unsigned I get the expected result. I don't understand why, though

unsigned would then appear to be 32 bits wide.

I need BinT to be 32-bits wide

Given that requirement, you probably should avoid bit fields if you want your program to be portable. If you use bit fields, then you depend on the implementation defined behaviour.

like image 90
eerorika Avatar answered Mar 17 '23 20:03

eerorika