Bit-Field Storage Unit Boundaries in C
In the C programming language, bit-fields allow developers to allocate specific numbers of bits within structure members to optimize memory usage or map directly to hardware registers. When consecutive bit-fields require more bits than remain in the current allocation unit, the behavior is explicitly classified by the ISO C standard as implementation-defined. This article examines the standard's requirements, how mainstream compilers handle boundary straddling, and the practical implications for portable software development.
The ISO C Standard Rule
According to the ISO/IEC 9899 standard (specifically within Section 6.7.2.1 covering structure and union specifiers), the allocation of bit-fields within an addressable storage unit is determined by the compiler implementation. The standard states:
"If enough space remains, a bit-field 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."
Because this behavior is implementation-defined, compilers must document how they handle the scenario, but they are not mandated to choose one approach over the other.
How Compilers Handle Boundary Straddling
When a bit-field does not fit into the remaining bits of a storage unit, an implementation typically adopts one of two approaches:
1. Moving to the Next Unit (Padding)
The dominant approach in modern compilers—including GCC, Clang, and Microsoft Visual C++ (MSVC)—is to avoid straddling storage boundaries entirely. When a bit-field cannot fit into the remaining bits of the current unit, the compiler leaves those remaining bits unused as padding and begins allocating the field at the start of the next storage unit.
Consider this example where unsigned int is 32 bits:
struct Status {
unsigned int flags : 28;
unsigned int code : 8;
};Here, flags consumes 28 bits, leaving 4 bits in the
initial 32-bit storage unit. Because code requires 8 bits,
it cannot fit in the 4 remaining bits. Most compilers will pad the
remaining 4 bits and allocate code within a new 32-bit
storage unit, causing sizeof(struct Status) to evaluate to
8 bytes rather than 5.
2. Splitting Across Units (Straddling)
Some architectures and specialized compilers (especially in targeted
embedded environments) permit a bit-field to split across two adjacent
storage units. In such an implementation, the first 4 bits of
code would occupy the end of the first 32-bit word, while
the remaining 4 bits would occupy the beginning of the second word.
While this approach minimizes memory usage, it introduces performance penalties. The CPU must execute multiple memory access and shift instructions (read-modify-write sequences) to assemble or write a single logical value that crosses a word boundary.
Controlling Boundary Alignment with Zero-Width Bit-Fields
C provides a standardized mechanism to force alignment to the next storage boundary: the unnamed zero-width bit-field.
struct ExplicitLayout {
unsigned int a : 10;
unsigned int : 0; /* Forces alignment to next storage boundary */
unsigned int b : 10;
};Specifying a bit-field with a width of 0 indicates that
no further bit-fields are to be packed into the current storage unit,
guaranteeing that the subsequent member starts at the boundary of the
next unit across all standard-compliant compilers.
Portability Considerations
Because boundary straddling, bit ordering (endianness within the
unit), and padding are all implementation-defined, relying on specific
bit-field layouts for hardware register mapping or network protocols is
inherently non-portable. When exact memory representations or boundary
spans are required, explicit bitwise shift and mask operations
(<<, >>, &,
|) applied to fixed-width integer types (such as
uint32_t from <stdint.h>) provide
deterministic, cross-platform behavior.