C Mastery / Bit-Fields
Part 1 — The Core Language

Bit-Fields

This chapter covers bit-fields: struct members that occupy a specified number of bits rather than whole bytes. They are used for hardware registers, packed protocol fields, and flags, but their layout is highly implementation-defined.

Why This Matters

Bit-fields are the convenient, self-documenting way to pack small values into an integer-sized unit. However, their bit ordering and padding are implementation-defined, which makes them dangerous for portable binary formats or cross-compiler hardware access.

Prerequisites

Core Concept

A bit-field is a struct member declared with a width:

struct Flags {
    unsigned int a : 1;
    unsigned int b : 3;
    unsigned int c : 4;
};

Each member occupies the specified number of bits. The struct packs them into an addressable storage unit (typically an unsigned int), with layout determined by the implementation.

Bit-fields are normally declared as unsigned int, signed int, or _Bool (and, in C99, other implementation-defined types). Using plain int is allowed but implementation-defined in signedness.

Syntax

struct Name {
    type member : width;
};

The width must be a non-negative integer constant expression and cannot exceed the width of the declared type.

Examples

Basic flags

#include <stdio.h>

struct Flags {
    unsigned int ready  : 1;
    unsigned int error  : 1;
    unsigned int mode   : 2;
};

int main(void)
{
    struct Flags f = {0};
    f.ready = 1;
    f.mode = 2;
    printf("ready=%u error=%u mode=%u\n", f.ready, f.error, f.mode);
    return 0;
}

Expected output: ready=1 error=0 mode=2.

How It Works

The compiler allocates one or more addressable storage units (typically unsigned int) and packs bit-fields into them. The order of allocation (low bit first vs. high bit first), whether fields can straddle unit boundaries, and the padding between them are all implementation-defined.

Variations

Bit-fields vs. manual masking

You can achieve the same effect with shifts and masks (c.emb.mask), which is fully portable but more verbose. Bit-fields are more readable but less portable in layout.

Unnamed and zero-width bit-fields

struct S {
    unsigned int a : 1;
    unsigned int   : 0;   /* force next field to new unit */
    unsigned int b : 1;
};

Common Mistakes

Undefined Behavior

aligned at byte boundaries and are not objects in the usual sense). VERIFIED

underlying storage in a way that violates aliasing is UB.

Portability

VERIFIED

signed int or unsigned int explicitly.

shift/mask for that.

Under the Hood

The compiler generates shift-and-mask sequences for bit-field access: read the storage unit, mask out the field, shift to position, and write back. This is why bit-field access can be slightly more expensive than a plain variable access but far more readable than manual bit manipulation.

Practical Usage

of layout is not required.

be portable and bit-exact.

Exercises

1. Define a struct with bit-fields of various widths and print sizeof and each field's value. 2. Compare a bit-field struct with an equivalent manual mask/shift implementation. 3. Demonstrate that you cannot take the address of a bit-field (observe the compiler error). 4. Use a zero-width bit-field to force alignment and observe the effect on sizeof.

Deep Challenge

Explain, using the C standard's rules, why bit-fields are unsafe for reading a hardware register on a different compiler or architecture, and write a portable mask/shift alternative for a 32-bit register with a 4-bit field and a 1-bit flag. Discuss the trade-offs.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.struct.bitfield06