πŸ‡―πŸ‡΅ ζ—₯本θͺž | πŸ‡ΊπŸ‡Έ English

C Unions

Multiple members sharing a single memory slot. Best learned alongside struct.

πŸ“– What you'll learn on this page
βœ… Must-know essentials
  • All union members share a single memory slot
  • sizeof equals the size of the largest member
  • Only one member is valid at a time
⭐ Read if you have time
  • The classic endianness check
  • Tagged unions (enum + union)
  • Type punning in C11
πŸ’ͺ Don't worry if it doesn't click right away
Unions layer several views over one memory slot β€” abstract, and rarely needed day to day. Feel free to skim.
How to retry
  1. Get comfortable with structs first
  2. Verify with sizeof that the size matches the largest member
  3. The endianness check is the most practical takeaway
  4. If it doesn't click, move on β€” come back when you hit a real use case
πŸ’‘ Tip: Real-world uses are tagged unions and type punning. For beginners, mastering struct is enough.

What is a union

A union is a type whose members all share the same memory. Its size equals that of its largest member, and only one member holds a meaningful value at a time.
#include <stdio.h>

union Data {
    int    i;
    float  f;
    char   s[8];
};

int main(void) {
    union Data d;

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

    d.f = 3.14f;                    // i becomes invalid now
    printf("d.f = %f\n", d.f);
    printf("d.i = %d\n", d.i);        // bit pattern of the float

    printf("sizeof = %zu\n", sizeof(d));// 8 (largest member)
    return 0;
}
How it differs from struct: A struct places members side by side (the total is the sum of their sizes); a union overlays them (the total is the size of the largest).

struct vs union (memory picture)

struct S { int a; int b; }

a
a
a
a
a
0-3
b
b
b
b
b
4-7
sizeof = 8 bytes. a and b are stored independently.

union U { int a; int b; }

a = b
a/b
a/b
a/b
a/b
0-3
sizeof = 4 bytes. Writing a also changes b β€” they share the same location.

Experiment

#include <stdio.h>

struct S { int a; int b; };
union  U { int a; int b; };

int main(void) {
    struct S s = {10, 20};
    printf("struct: a=%d b=%d size=%zu\n", s.a, s.b, sizeof(s));
    // β†’ a=10 b=20 size=8

    union U u;
    u.a = 10;
    u.b = 20;
    printf("union:  a=%d b=%d size=%zu\n", u.a, u.b, sizeof(u));
    // β†’ a=20 b=20 size=4   (same slot!)
}

Size & alignment

A union is at least as large as its biggest member, and alignment requirements may add trailing padding.
union Mix {
    char   c;       // 1 byte
    int    i;       // 4 bytes
    double d;       // 8 bytes
};
// sizeof(union Mix) is at least 8, aligned to double

Viewing the same slot two ways

union Split {
    int           n;
    struct { char b0, b1, b2, b3; } bytes;
};

union Split s;
s.n = 0x12345678;
printf("%02x %02x %02x %02x\n",
       (unsigned)s.bytes.b0, (unsigned)s.bytes.b1,
       (unsigned)s.bytes.b2, (unsigned)s.bytes.b3);
// little-endian: 78 56 34 12
// big-endian:    12 34 56 78

Endianness check

Unions offer a clean way to check whether the current CPU is little- or big-endian. It's a classic textbook example.
#include <stdio.h>

int is_little_endian(void) {
    union { int i; char c[sizeof(int)]; } u;
    u.i = 1;
    return u.c[0] == 1;
}

int main(void) {
    printf("%s-endian\n",
           is_little_endian() ? "little" : "big");
}

Why it works

The 4 bytes of int i = 1:
little-endian (x86 etc.): 01 00 00 00 β†’ c[0] == 1
big-endian (some ARM etc.): 00 00 00 01 β†’ c[0] == 0
Without a union: int n = 1; char *p = (char*)&n; works too, but the union version is generally friendlier to the compiler's strict-aliasing rules.

Tagged union (variant pattern)

When a value can be one of several types, pair a type tag (an enum) with a data union inside a struct. It's C's take on a sum type, similar to Rust's enum.
#include <stdio.h>

enum ValueKind { V_INT, V_FLOAT, V_STRING };

struct Value {
    enum ValueKind kind;        // "which is active"
    union {
        int   i;
        float f;
        char  s[32];
    } data;
};

void print_value(struct Value v) {
    switch (v.kind) {
        case V_INT:    printf("int: %d\n", v.data.i);    break;
        case V_FLOAT:  printf("float: %f\n", v.data.f); break;
        case V_STRING: printf("str: %s\n", v.data.s);   break;
    }
}

int main(void) {
    struct Value a = {V_INT, .data.i = 42};
    struct Value b = {V_FLOAT, .data.f = 3.14f};
    struct Value c;
    c.kind = V_STRING;
    snprintf(c.data.s, sizeof(c.data.s), "hello");

    print_value(a);  // int: 42
    print_value(b);  // float: 3.14
    print_value(c);  // str: hello
}
Common in JSON parsers, ASTs, and protocol messages. Wrap reads and writes behind functions so callers can't forget to check kind.

Size considerations

// sizeof(struct Value) = enum(4) + largest member(32) + padding
// even a small int value pays the 32-byte string slot

Pitfalls & when to use

Gotchas

When unions shine

  1. Saving memory when you know only one field is ever active (common in embedded systems)
  2. Variant types: JSON values, AST nodes, and protocol messages (paired with a tag)
  3. Inspecting the bit representation: endianness checks and bit patterns
  4. Hardware register maps: the same register seen both as bits and as a word
Modern alternatives: If you just need to save memory, a void* + size pair, C++'s std::variant, or Rust's enum is safer. In C, a tagged union with wrapper functions is the pragmatic choice.

Challenges

Challenge 1: float bit pattern
Use union { float f; uint32_t u; } to print the 32-bit hex representation of f = 1.0f (IEEE-754 gives 0x3F800000).
Challenge 2: is_little_endian()
Implement the function from the text on your own machine and verify the result. Most Mac, Linux, and Windows desktops are little-endian.
Challenge 3: Tiny value type
Build a tagged union struct Value that holds int, float, or bool. Implement Value add(Value a, Value b) so that numeric types add, two bools OR together, and mismatched kinds print an error.
Challenge 4: RGBA color
Create a union of a 32-bit integer and four 8-bit bytes (R, G, B, A). Pack and unpack 0xRRGGBBAA through both views.

See also

Review Quiz

Check your understanding of this lesson.

Q1. Which statement about a union is correct?

All members share the same memory region
Each member has its own independent memory
Accessing any member other than the last one assigned raises an error

A union lets you view a single memory region as any one of several types. Only one member is valid at a time.

Q2. What's the typical size of union U { int i; char c; };?

The size of the largest member (int)
The sum of all members
Always the same size as a pointer

A union's size matches its largest member, possibly with a small amount of padding for alignment.

Q3. What's a typical use for a union?

A tagged union that pairs a type tag with the data to hold values of multiple types
Implementing a dynamic array
Function overloading

C has no overloading, so enum + union is the standard pattern for switching between types like int and float.