Skip to content

C library hygiene: Avoid use of bitfields #4

@amboar

Description

@amboar

WG14/N1256 (C99 draft standard) section 6.7.2.1 paragraphs 10 and 11 state:

An implementation may allocate any addressable storage unit large enough to hold a bitfield. If enough space remains, a bitfield that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.

A bit-field declaration with no declarator, but only a colon and a width, indicates an unnamed bit-field.108) As a special case, a bit-field structure member with a width of 0 indicates that no further bit-field is to be packed into the unit in which the previous bitfield, if any, was placed.

with the following footnote:

  1. An unnamed bit-field structure member is useful for padding to conform to externally imposed layouts.

Essentially, the implementation defined bitfield order leads to duplicate struct member definitions such as

#if defined(__LITTLE_ENDIAN_BITFIELD)
uint8_t instance_id : 5; //!< Instance ID
uint8_t reserved : 1; //!< Reserved
uint8_t datagram : 1; //!< Datagram bit
uint8_t request : 1; //!< Request bit
#elif defined(__BIG_ENDIAN_BITFIELD)
uint8_t request : 1; //!< Request bit
uint8_t datagram : 1; //!< Datagram bit
uint8_t reserved : 1; //!< Reserved
uint8_t instance_id : 5; //!< Instance ID
#endif

Further, as multiple bitfields can be packed into a single storage unit, write-on-write ordering of read-modify-write sequences can stomp on values in concurrent environments (e.g. via signal handling or multi-threading).

IMO we should avoid use of bitfields and prefer use of explicitly sized types and use of macros for named shift and mask constants to encode and decode sub-unit fields. The consequence of this is we're always operating above the behaviour of endianness, which removes the need to double-define struct members.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions