Search code examples
c++bit-fields

How does compiler determine the size of a bitfield struct?


For example:

struct a {
    uint32_t    foreColor_ : 32;
    uint32_t    backColor_ : 32;
    uint16_t    lfHeight_ : 16;
    uint16_t    flags_: 4;
    bool    lfBold_: 1;
    bool    lfItalic_: 1;
    bool    lfUnderLine_: 1;
    bool    lfDashLine_: 1; 
    bool    lfStrike_: 1;
    bool    lfSubscript_: 1;
    bool    lfSuperscript_: 1;
};

is 16 bytes but

struct a {
    uint32_t    foreColor_ : 32;
    uint32_t    backColor_ : 32;
    uint16_t    lfHeight_ : 16;
    uint8_t     flags_: 4;
    bool    lfBold_: 1;
    bool    lfItalic_: 1;
    bool    lfUnderLine_: 1;
    bool    lfDashLine_: 1; //for ime 
    bool    lfStrike_: 1;
    bool    lfSubscript_: 1;
    bool    lfSuperscript_: 1;
};

is 12 bytes long.

I thought flags_ should have the same length, but it seems not.

Why?


Solution

  • The standard (9.6 of the working draft) says this:

    specifies a bit-field; its length is set off from the bit-field name by a colon. The bit-field attribute is not part of the type of the class member. The constant-expression shall be an integral constant-expression with a value greater than or equal to zero. The constant-expression may be larger than the number of bits in the object representation ( 3.9 ) of the bit-field’s type; in such cases the extra bits are used as padding bits and do not participate in the value representation ( 3.9 ) of the bit-field. Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined. Bit-fields are packed into some addressable allocation unit. [ Note: bit-fields straddle allocation units on some machines and not on others. Bit-fields are assigned right-to-left on some machines, left-to-right on others. —end note]

    (my emphasis)

    So it will depend on your compiler. What appears to be happening in your case - and I would describe as fairly normal behaviour - is that it is only combining bitfields of the same type and then is packing the structure to a 4 byte boundary, so in the first case we have:

    struct a {
        uint32_t    foreColor_ : 32; // 4 bytes (total)
        uint32_t    backColor_ : 32; // 8 bytes
        uint16_t    lfHeight_ : 16;  // 10 bytes
        uint16_t    flags_: 4;       // 12 bytes
        bool    lfBold_: 1;          // 13 bytes
        bool    lfItalic_: 1;
        bool    lfUnderLine_: 1;
        bool    lfDashLine_: 1; 
        bool    lfStrike_: 1;
        bool    lfSubscript_: 1;
        bool    lfSuperscript_: 1;   // still 13 bytes
    };
    

    Which is then padded to 16 bytes, and in the second we have:

    struct a {
        uint32_t    foreColor_ : 32;  // 4 bytes (total)
        uint32_t    backColor_ : 32;  // 8 bytes
        uint16_t    lfHeight_ : 16;   // 10 bytes
        uint8_t     flags_: 4;        // 11 bytes
        bool    lfBold_: 1;           // 12 bytes
        bool    lfItalic_: 1;
        bool    lfUnderLine_: 1;
        bool    lfDashLine_: 1; 
        bool    lfStrike_: 1;
        bool    lfSubscript_: 1;
        bool    lfSuperscript_: 1;    // still 12 bytes
    };
    

    Which needs no padding and stays at 12 bytes.