thecodex.expert · The Codex Family of Knowledge
C

Unions

Every member of a union starts at the exact same memory address — writing to one member and reading a different one is legal, and it’s exactly how low-level code reinterprets raw bytes.

C17 / C23 All members share one address Last verified:
Canonical Definition

Unlike a struct, where every member gets its own dedicated memory and the struct's total size is roughly the sum of all members, a union's members all overlap the SAME memory — the union's size is just large enough to hold its biggest member, and writing to one member overwrites whatever was in any other member sharing that space. This makes a union useful specifically when only one of several possible interpretations of a value is needed at a time, and it's the classic mechanism for type punning — reinterpreting the same raw bytes as a different type.

All members share one address

sizeof(Value) equals sizeof(double) (its largest member) — not the sum of int + float + double the way a struct would be. Setting data.d then reading data.i would give nonsense, since they're literally the same bytes interpreted as two different types.

Cunion_basics.c
union Value {
    int i;
    float f;
    double d;
};

union Value data;
data.i = 42;
printf("%d\n", data.i);   // 42

data.d = 3.14;              // OVERWRITES the same memory data.i used
printf("%d\n", data.i);    // garbage — not 42 anymore, i's bytes were reinterpreted

printf("%zu\n", sizeof(union Value));   // size of the LARGEST member (double), not the sum

Tagged unions: tracking which member is actually valid

Since a union alone can't tell you which member was last written, pairing it with a separate "tag" field (often inside a wrapping struct) is the idiomatic way to safely know which interpretation is currently meaningful — this is C's rough equivalent of a discriminated union or a Rust enum with data.

Ctagged_union.c
enum ValueType { TYPE_INT, TYPE_FLOAT };

struct TaggedValue {
    enum ValueType type;   // the tag: tells you which member is currently valid
    union {
        int i;
        float f;
    } value;
};

struct TaggedValue v = { .type = TYPE_INT, .value.i = 42 };
if (v.type == TYPE_INT) {
    printf("%d\n", v.value.i);   // safe — we KNOW it's the int member
}

Type punning: examining a value's raw bytes

This lets low-level code inspect a float's underlying bit pattern directly — a common technique in numerical and embedded code, though the C standard technically only fully guarantees this specific pattern (reading a different member than was last written) works portably as of more recent standard clarifications; it has long been common practice regardless.

Ctype_punning.c
union FloatBits {
    float f;
    unsigned int bits;
};

union FloatBits fb;
fb.f = 1.0f;
printf("%x\n", fb.bits);   // the raw IEEE 754 bit pattern of 1.0f, as a hex integer

Sources

1
ISO/IEC 9899 (C Standard), "Union types," cppreference.com/w/c/language/union.