Using bitfield-structs in binary de-serialization: is layout guaranteed on GCC?

So, I’m writing a struct that’s going to be used for de-serializing a binary stream of data. To get the point across, here is a cut-down version:

typedef struct
{
    bool flag1 : 1;
    bool flag2 : 1;
    bool flag3 : 1;
    bool flag4 : 1;
    uint32_t reserved : 28;
} frame_flags_t;

typedef struct
{
    /* Every frame starts with a magic value. */
    uint32_t magic;

    frame_flags_t flags;

    uint8_t reserved_1;
    
    /* A bunch of other things */

    uint32_t crc;
} frame_t;

My question is, if do the following:

frame_t f;

memcpy(&f, raw_data_p, sizeof(frame_t));

Am I guaranteed that f.flags.flag1 is really the first bit (after the magic member, assuming a neatly packed struct (which it is))? And that .flags2 will be the one following that, and etc?

From what I understand the C and C++ standards don’t guarantee this. Does GCC?

>Solution :

Am I guaranteed that f.flags.flag1 is really the first bit (after the magic member, assuming a neatly packed struct (which it is))?

The C language does not guarantee that, no.

And that .flags2 will be the one following that, and etc?

The C language does require that consecutive bitfields assigned to the same addressable storage unit be laid out without gaps between them. That is likely to mean that the flags end up occupying adjacent bits in the same byte, but it does not have to mean that.

From what I understand the C and C++ standards don’t guarantee this. Does GCC?

No. Structure layout rules are a characteristic of an application binary interface (ABI), which is a property of operating system + hardware combinations. For example, there is an ABI for Linux running on x86_64, a different one for Linux running on 32-bit x86, and still different ones for Windows running on those platforms. GCC supports a wide variety of ABIs, and it lays out structures according to the rules of the target ABI. It cannot make any blanket guarantees about details of structure layout.

For example, the relevant ABI for Linux / x86_64 is https://www.intel.com/content/dam/develop/external/us/en/documents/mpx-linux64-abi.pdf. With respect to bitfield layout, it says:

Bit-fields obey the same size and alignment rules as other structure
and union members.

Also:

  • bit-fields are allocated from right to left
  • bit-fields must be contained in a storage unit appropriate for its declared type
  • bit-fields may share a storage unit with other struct / union members

That’s actually not altogether consistent, but the way it’s interpreted for your frame_flags_t is that:

  • the structure has size 4, consisting of a single "addressible storage unit" of that size into which all the bitfields are packed
  • flag1 uses the least-significant bit
  • flag2 uses the second-least-significant bit
  • flag3 uses the third-least-significant bit
  • flag4 uses the fourth-least-significant bit

Furthermore, the overall frame_t structure has a 4-byte alignment requirement on Linux / x86_64, and it will be laid out with the minimum padding required to align all members. On such a machine, therefore, there will be no padding between the magic member and the flags member. x86_64 is little-endian, so that will indeed put the flag bits in the first byte following magic on Linux / x86_64.

Leave a Reply