Unions — 10 Examples
0%
Unions  ·  10 Examples

Unions in C —
10 Programs

Ten programs from basic to practical — shared memory, type punning, tagged unions, bit inspection, embedded protocols, and more. Each one teaches a different union technique.

1
First Union
2
Shared Memory
3
Union vs Struct
4
Tagged Union
5
Byte Inspector
6
Float Bits
7
Union in Struct
8
Network Packet
9
Union Array
10
Variant Record
1
🔰 Your First Union — One Slot, Three Types
Define a union, store one value at a time, see shared memory in action
Basics
A union looks exactly like a struct — but all members share the same block of memory. The union is only large enough to hold its biggest member. You can store an int or a float or a char — but only one at a time. Writing to one member overwrites what the others see. Here we store each type in turn and read it back immediately.
ex1_first_union.c
C
#include <stdio.h>

union Data {
    int   i;
    float f;
    char  c;
};

int main() {
    union Data d;

    /* Store int — read int immediately */
    d.i = 42;
    printf("Stored int   : d.i = %d\n", d.i);

    /* Store float — overwrites the int bytes */
    d.f = 3.14f;
    printf("Stored float : d.f = %.2f\n", d.f);

    /* Store char — overwrites the float bytes */
    d.c = 'A';
    printf("Stored char  : d.c = %c\n", d.c);

    /* Size — only as big as the largest member (float = 4 bytes) */
    printf("\nsizeof(union Data) = %zu bytes\n", sizeof(union Data));
    printf("sizeof(int)        = %zu bytes\n", sizeof(int));
    printf("sizeof(float)      = %zu bytes\n", sizeof(float));
    printf("sizeof(char)       = %zu bytes\n", sizeof(char));
    return 0;
}
output
Stored int   : d.i = 42
Stored float : d.f = 3.14
Stored char  : d.c = A

sizeof(union Data) = 4 bytes
sizeof(int)        = 4 bytes
sizeof(float)      = 4 bytes
sizeof(char)       = 1 byte
Union size rule: sizeof(union) = size of its largest member. Here both int and float are 4 bytes, so the union is 4 bytes total — regardless of how many members it has.
example 2
2
🧠 Shared Memory — Reading Stale Values
Write one member, read another — see what shared memory really means
Shared Memory
The most important thing to understand about unions: writing one member and reading a different member gives you the raw bytes of the first value interpreted as the second type. This is called reading a stale member. The program intentionally shows this — write an int, then read .f and .c to see the reinterpreted bytes. Understanding this prevents hard-to-find bugs.
ex2_shared_memory.c
C
#include <stdio.h>

typedef union {
    int   i;
    float f;
    char  c;
} Data;

int main() {
    Data d;

    /* Write only .i */
    d.i = 1065353216;   /* 0x3F800000 in hex */

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

    /* Read ALL members — they all see the same 4 bytes */
    printf("Read d.i = %d\n",    d.i);  /* correct: 1065353216 */
    printf("Read d.f = %f\n",    d.f);  /* 0x3F800000 = 1.0f exactly */
    printf("Read d.c = %d (0x%02X)\n", d.c, (unsigned char)d.c);
    /* .c reads only the first byte of the 4-byte int */

    printf("\n--- All 4 bytes of the union ---\n");
    unsigned char *p = (unsigned char*)&d;
    for (int k = 0; k < 4; k++)
        printf("  byte[%d] = 0x%02X\n", k, p[k]);
    return 0;
}
output
Wrote   d.i = 1065353216

Read d.i = 1065353216
Read d.f = 1.000000
Read d.c = 0 (0x00)

--- All 4 bytes of the union ---
  byte[0] = 0x00
  byte[1] = 0x00
  byte[2] = 0x80
  byte[3] = 0x3F
memory layout — all members overlap at the same address
d.i (int)
0x00
0x00
0x80
0x3F
← 4 bytes
d.f (float)
0x00
0x00
0x80
0x3F
← same 4 bytes = 1.0f
d.c (char)
0x00
·
·
·
← only 1 byte visible
Only the last-written member is valid to read. Reading a different member is technically undefined behaviour in standard C (though widely used in practice). Always track which member you last wrote — that is the only safe one to read. The tagged union in Example 4 solves this properly.
example 3
3
⚖️ Union vs Struct — Size Comparison
Same fields, different containers — see the memory difference clearly
Size Compare
The clearest way to understand unions is to put the same fields in both a struct and a union and compare their sizes. The struct allocates memory for every field — they all live at different addresses. The union gives them all the same address — total size = largest field only. This is the fundamental trade-off: union saves memory but holds only one value at a time.
ex3_union_vs_struct.c
C
#include <stdio.h>

/* Struct — every field gets its OWN memory */
typedef struct {
    char   c;    /* 1 byte  (+ 3 padding) */
    int    i;    /* 4 bytes */
    float  f;    /* 4 bytes */
    double d;    /* 8 bytes */
} MyStruct;   /* total ≈ 24 bytes (with alignment) */

/* Union — ALL fields share the SAME memory */
typedef union {
    char   c;    /* 1 byte */
    int    i;    /* 4 bytes */
    float  f;    /* 4 bytes */
    double d;    /* 8 bytes ← biggest */
} MyUnion;    /* total = 8 bytes (= sizeof double) */

int main() {
    MyStruct s;
    MyUnion  u;

    printf("--- sizeof ---\n");
    printf("MyStruct : %zu bytes\n", sizeof(MyStruct));
    printf("MyUnion  : %zu bytes\n", sizeof(MyUnion));

    printf("\n--- struct member addresses ---\n");
    printf("&s.c = %p\n", (void*)&s.c);
    printf("&s.i = %p\n", (void*)&s.i);
    printf("&s.f = %p\n", (void*)&s.f);
    printf("&s.d = %p\n", (void*)&s.d);

    printf("\n--- union member addresses ---\n");
    printf("&u.c = %p\n", (void*)&u.c);
    printf("&u.i = %p\n", (void*)&u.i);
    printf("&u.f = %p\n", (void*)&u.f);
    printf("&u.d = %p  ← all same!\n", (void*)&u.d);
    return 0;
}
output
--- sizeof ---
MyStruct : 24 bytes
MyUnion  :  8 bytes

--- struct member addresses ---
&s.c = 0x7ffd1000
&s.i = 0x7ffd1004
&s.f = 0x7ffd1008
&s.d = 0x7ffd1010

--- union member addresses ---
&u.c = 0x7ffd2000
&u.i = 0x7ffd2000
&u.f = 0x7ffd2000
&u.d = 0x7ffd2000  ← all same!
Every union member starts at address 0. They all share the same starting address — the union's own address. The struct members are spread across 24 bytes. The union uses only 8 — a 3× memory saving here.
example 4
4
🏷️ Tagged Union — Safe One-of-Many Value
A struct wraps the union with a tag field — always know which member is active
Tagged Union
The tagged union (also called a discriminated union) is the correct, safe way to use a union. A surrounding struct adds a tag field — an enum or int that records which member was last written. Before reading, check the tag. This pattern is used in compilers, interpreters, JSON parsers, and virtually every real-world union use case.
ex4_tagged_union.c
C
#include <stdio.h>
#include <string.h>

/* Tag: tells us which member is currently valid */
typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STR } Tag;

typedef struct {
    Tag tag;          /* which field is active?  */
    union {
        int   i;
        float f;
        char  str[20];
    } val;            /* the shared memory slot */
} Value;

/* Safe print — checks tag before reading */
void printValue(const Value *v) {
    switch (v->tag) {
        case TYPE_INT:   printf("int   : %d\n",   v->val.i);   break;
        case TYPE_FLOAT: printf("float : %.2f\n", v->val.f);   break;
        case TYPE_STR:   printf("str   : \"%s\"\n",v->val.str); break;
    }
}

int main() {
    Value a, b, c;

    a.tag   = TYPE_INT;
    a.val.i = 42;

    b.tag   = TYPE_FLOAT;
    b.val.f = 3.14f;

    c.tag = TYPE_STR;
    strcpy(c.val.str, "Ananta");

    printf("--- Tagged values ---\n");
    printValue(&a);
    printValue(&b);
    printValue(&c);

    /* Change type safely — update BOTH tag and value */
    printf("\nChanging a from int to float...\n");
    a.tag   = TYPE_FLOAT;
    a.val.f = 9.99f;
    printValue(&a);
    return 0;
}
output
--- Tagged values ---
int   : 42
float : 3.14
str   : "Ananta"

Changing a from int to float...
float : 9.99
Always update the tag alongside the value. The pattern is always: set tag = TYPE_X, then set val.x = .... The switch(v->tag) in printValue guarantees you only ever read the correct member. This is how Rust's enums and C++'s std::variant work internally.
example 5
5
🔬 Byte Inspector — See Inside an Integer
Store an int, read it byte-by-byte through an unsigned char array member
Byte Inspect
A classic union trick: one member is an int, the other is an array of 4 unsigned char. Since they share memory, writing the int and reading the char array gives you each individual byte of the integer. This reveals the endianness of the machine — whether the least-significant byte is stored first (little-endian) or last (big-endian).
ex5_byte_inspector.c
C
#include <stdio.h>

typedef union {
    unsigned int   value;           /* full 32-bit integer */
    unsigned char  bytes[4];       /* same 4 bytes, one at a time */
} IntBytes;

void inspect(unsigned int n) {
    IntBytes ib;
    ib.value = n;
    printf("Value      : %u  (0x%08X)\n", ib.value, ib.value);
    printf("Bytes      : ");
    for (int i = 0; i < 4; i++)
        printf("[%d]=0x%02X  ", i, ib.bytes[i]);
    printf("\n");
    /* On little-endian: bytes[0] holds the LEAST significant byte */
    printf("Endianness : %s-endian\n\n",
           ib.bytes[0] == (n & 0xFF) ? "little" : "big");
}

int main() {
    inspect(0x01020304);   /* 4 distinct bytes */
    inspect(255);           /* 0x000000FF */
    inspect(65536);         /* 0x00010000 */
    return 0;
}
output (little-endian system — most x86 PCs)
Value      : 16909060  (0x01020304)
Bytes      : [0]=0x04  [1]=0x03  [2]=0x02  [3]=0x01
Endianness : little-endian

Value      : 255  (0x000000FF)
Bytes      : [0]=0xFF  [1]=0x00  [2]=0x00  [3]=0x00
Endianness : little-endian

Value      : 65536  (0x00010000)
Bytes      : [0]=0x00  [1]=0x00  [2]=0x01  [3]=0x00
Endianness : little-endian
Little-endian (x86, ARM, most modern CPUs): byte[0] holds the least significant byte. So for 0x01020304, byte[0]=0x04, byte[3]=0x01. Big-endian (network order, older MIPS/SPARC): it's reversed — byte[0]=0x01. This matters whenever you send binary data over a network.
example 6
6
🔢 Float Bit Viewer — Sign, Exponent, Mantissa
Overlay a float with an unsigned int to read its raw IEEE 754 bits
Type Punning
An IEEE 754 float is 32 bits arranged as: 1 sign bit + 8 exponent bits + 23 mantissa bits. By storing a float in a union alongside an unsigned int, you can read the raw bit pattern and extract each field using bitwise operators — no casting, no undefined behaviour. This is the standard technique for type punning in C.
ex6_float_bits.c
C
#include <stdio.h>

typedef union {
    float        f;
    unsigned int bits;   /* same 4 bytes as the float */
} FloatBits;

void showBits(float x) {
    FloatBits fb;
    fb.f = x;                          /* write float */
    unsigned int b = fb.bits;           /* read as int */

    int sign     = (b >> 31) & 1;        /* bit 31 */
    int exponent = (b >> 23) & 0xFF;    /* bits 30..23 */
    int mantissa =  b & 0x7FFFFF;        /* bits 22..0 */

    printf("f = %g\n", x);
    printf("  binary    : ");
    for (int i = 31; i >= 0; i--) {
        printf("%d", (b >> i) & 1);
        if (i == 31 || i == 23) printf(" ");
    }
    printf("\n");
    printf("  sign      : %d\n",      sign);
    printf("  exponent  : %d (bias=%d)\n", exponent, exponent-127);
    printf("  mantissa  : 0x%06X\n\n", mantissa);
}

int main() {
    showBits(1.0f);
    showBits(-2.5f);
    showBits(0.0f);
    return 0;
}
output
f = 1
  binary    : 0 01111111 00000000000000000000000
  sign      : 0
  exponent  : 127 (bias=0)
  mantissa  : 0x000000

f = -2.5
  binary    : 1 10000000 01000000000000000000000
  sign      : 1
  exponent  : 128 (bias=1)
  mantissa  : 0x200000

f = 0
  binary    : 0 00000000 00000000000000000000000
  sign      : 0
  exponent  : 0 (bias=-127)
  mantissa  : 0x000000
Type punning via union is legal in C (C99 and later). The same trick via pointer casts (*(int*)&f) is undefined behaviour in C and C++. Always use the union method when you need to inspect raw bytes of a floating-point number.
example 7
7
📦 Union Inside a Struct — Product Variants
Different product types need different fields — one struct handles all using an inner union
Union in Struct
A Product struct uses an embedded union to hold type-specific data. A Book needs an ISBN and page count. A Drink needs volume in ml. A Cloth needs a size string. Without a union you'd waste memory keeping all fields for every product. With a union, each product object is exactly as big as the largest variant — and only that variant's memory is used.
ex7_union_in_struct.c
C
#include <stdio.h>
#include <string.h>

typedef enum { BOOK, DRINK, CLOTH } ProductType;

typedef struct {
    char        name[25];
    float       price;
    ProductType type;
    union {                      /* only one is ever used */
        struct {
            char isbn[14];
            int  pages;
        } book;
        struct {
            int ml;
        } drink;
        struct {
            char size[5];    /* S, M, L, XL */
        } cloth;
    } info;
} Product;

void printProduct(const Product *p) {
    printf("%-18s Rs%7.2f  |  ", p->name, p->price);
    switch (p->type) {
        case BOOK:  printf("ISBN:%s  pages:%d\n",
                           p->info.book.isbn, p->info.book.pages); break;
        case DRINK: printf("volume:%dml\n", p->info.drink.ml);   break;
        case CLOTH: printf("size:%s\n",    p->info.cloth.size);  break;
    }
}

int main() {
    Product items[3];

    strcpy(items[0].name,  "Let Us C");
    items[0].price = 350.0f;
    items[0].type  = BOOK;
    strcpy(items[0].info.book.isbn, "978-8131722329");
    items[0].info.book.pages = 680;

    strcpy(items[1].name,  "Mango Juice");
    items[1].price = 45.0f;
    items[1].type  = DRINK;
    items[1].info.drink.ml = 250;

    strcpy(items[2].name,  "Cotton T-Shirt");
    items[2].price = 499.0f;
    items[2].type  = CLOTH;
    strcpy(items[2].info.cloth.size, "XL");

    printf("%-18s %10s  |  Details\n", "Product", "Price");
    printf("%s\n", "-------------------------------------------------------");
    for (int i = 0; i < 3; i++)
        printProduct(&items[i]);
    return 0;
}
output
Product             Price  |  Details
-------------------------------------------------------
Let Us C           Rs350.00  |  ISBN:978-8131722329  pages:680
Mango Juice         Rs45.00  |  volume:250ml
Cotton T-Shirt     Rs499.00  |  size:XL
This pattern — struct wrapping enum + union — appears everywhere in systems code. C compilers use it for AST nodes (a node is an if-statement or a function-call or a literal). JSON parsers use it for values. GUI frameworks use it for events.
example 8
8
📡 Network Packet — Parse Header Bytes
Store a 4-byte packet header as int or byte array — read fields either way
Embedded / Net
In networking and embedded systems, you often receive raw bytes and need to parse them as structured fields — or build structured fields and send them as raw bytes. A union with a uint32_t (whole word) and a struct of bitfields gives you both views simultaneously. Write the fields through the struct, read the wire bytes through the integer — no manual bit-shifting needed.
ex8_network_packet.c
C
#include <stdio.h>
#include <stdint.h>

/* 4-byte packet header laid out as bitfields */
typedef union {
    uint32_t raw;           /* the whole 32 bits as one word */
    unsigned char bytes[4]; /* individual wire bytes */
    struct {
        uint32_t version  : 4;   /* bits 0-3:  IP version */
        uint32_t ihl      : 4;   /* bits 4-7:  header length */
        uint32_t dscp     : 6;   /* bits 8-13: diff services */
        uint32_t ecn      : 2;   /* bits 14-15: congestion */
        uint32_t length   : 16;  /* bits 16-31: total length */
    } fields;
} IPv4Header;

int main() {
    IPv4Header hdr;

    /* Build header by setting fields */
    hdr.fields.version = 4;     /* IPv4 */
    hdr.fields.ihl     = 5;     /* 5 * 4 = 20 byte header */
    hdr.fields.dscp    = 0;
    hdr.fields.ecn     = 0;
    hdr.fields.length  = 60;    /* total packet length */

    printf("--- Structured view ---\n");
    printf("Version : %u\n", hdr.fields.version);
    printf("IHL     : %u (= %u bytes)\n", hdr.fields.ihl, hdr.fields.ihl*4);
    printf("Length  : %u\n", hdr.fields.length);

    printf("\n--- Raw wire bytes ---\n");
    printf("raw uint32 : 0x%08X\n", hdr.raw);
    for (int i = 0; i < 4; i++)
        printf("  byte[%d]  : 0x%02X\n", i, hdr.bytes[i]);

    /* Receive a raw packet and parse it */
    printf("\n--- Parsing received bytes ---\n");
    IPv4Header recv;
    recv.bytes[0] = 0x45;   /* version=4, ihl=5 */
    recv.bytes[1] = 0x00;
    recv.bytes[2] = 0x00;
    recv.bytes[3] = 0x3C;   /* 60 in big-endian high byte */
    printf("Version parsed: %u\n", recv.fields.version);
    printf("IHL     parsed: %u\n", recv.fields.ihl);
    return 0;
}
output
--- Structured view ---
Version : 4
IHL     : 5 (= 20 bytes)
Length  : 60

--- Raw wire bytes ---
raw uint32 : 0x003C0045
  byte[0]  : 0x45
  byte[1]  : 0x00
  byte[2]  : 0x3C
  byte[3]  : 0x00

--- Parsing received bytes ---
Version parsed: 4
IHL     parsed: 5
Bitfields + union is the standard pattern in every network driver, USB stack, and hardware register file in C. You write readable field names in code; the union gives you the raw bytes to send over the wire — no manual shifting or masking required.
example 9
9
🗂️ Array of Tagged Unions — Simple Symbol Table
Store ints, floats, and strings together in one array using tagged unions
Array of Unions
A common real-world need: store a mixed list of values — some integers, some floats, some strings — in a single array. An array of tagged unions makes this clean and type-safe. Each element knows its own type via the tag. This is exactly how a scripting language (Python, Lua, JavaScript) stores its variables internally in a C runtime.
ex9_union_array.c
C
#include <stdio.h>
#include <string.h>

typedef enum { INT, FLOAT, STRING } VarType;

typedef struct {
    char    varName[10];   /* variable name */
    VarType type;
    union {
        int   i;
        float f;
        char  s[20];
    } val;
} Symbol;

void printSymbol(const Symbol *sym) {
    printf("  %-8s = ", sym->varName);
    switch (sym->type) {
        case INT:    printf("%d   (int)\n",    sym->val.i); break;
        case FLOAT:  printf("%.2f (float)\n",  sym->val.f); break;
        case STRING: printf("\"%s\" (string)\n",sym->val.s); break;
    }
}

/* Look up a variable by name */
Symbol* lookup(Symbol *table, int n, const char *name) {
    for (int i = 0; i < n; i++)
        if (strcmp(table[i].varName, name) == 0)
            return &table[i];
    return NULL;
}

int main() {
    Symbol symtab[5];

    strcpy(symtab[0].varName, "age");
    symtab[0].type = INT; symtab[0].val.i = 21;

    strcpy(symtab[1].varName, "score");
    symtab[1].type = FLOAT; symtab[1].val.f = 95.5f;

    strcpy(symtab[2].varName, "city");
    symtab[2].type = STRING; strcpy(symtab[2].val.s, "Haridwar");

    strcpy(symtab[3].varName, "year");
    symtab[3].type = INT; symtab[3].val.i = 2024;

    strcpy(symtab[4].varName, "pi");
    symtab[4].type = FLOAT; symtab[4].val.f = 3.14159f;

    printf("--- Symbol Table ---\n");
    for (int i = 0; i < 5; i++)
        printSymbol(&symtab[i]);

    printf("\n--- Lookup ---\n");
    Symbol *found = lookup(symtab, 5, "city");
    if (found) printSymbol(found);

    found = lookup(symtab, 5, "missing");
    printf("\"missing\": %s\n", found ? "found" : "not found");
    return 0;
}
output
--- Symbol Table ---
  age      = 21   (int)
  score    = 95.50 (float)
  city     = "Haridwar" (string)
  year     = 2024   (int)
  pi       = 3.14 (float)

--- Lookup ---
  city     = "Haridwar" (string)
"missing": not found
This is how Python and Lua store all variables. Their C-level Value type is exactly this pattern — a tag plus a union. Every Python object has a type field and a data payload. The union makes the data payload exactly as large as needed — nothing wasted.
example 10
10
🏗️ Variant Record — Shape Calculator
Circle, rectangle, and triangle as one type — compute area for any shape
Complete App
The complete pattern — everything from examples 4, 7, and 9 combined into a real mini application. A Shape tagged union holds three shape variants (circle, rectangle, triangle). A single area() function checks the tag and computes the correct formula. An array of mixed shapes is processed in one loop — clean, memory-efficient, and easily extensible.
ex10_shape_calculator.c
C
#include <stdio.h>
#include <math.h>    /* compile with -lm */

typedef enum { CIRCLE, RECT, TRIANGLE } ShapeType;

typedef struct {
    ShapeType type;
    char      label[15];
    union {
        struct { double radius; }         circle;
        struct { double width, height; }   rect;
        struct { double base, height; }    tri;
    } dim;
} Shape;

/* Factory helpers */
Shape makeCircle(char *lbl, double r) {
    Shape s; s.type = CIRCLE;
    snprintf(s.label, 15, "%s", lbl);
    s.dim.circle.radius = r; return s;
}
Shape makeRect(char *lbl, double w, double h) {
    Shape s; s.type = RECT;
    snprintf(s.label, 15, "%s", lbl);
    s.dim.rect.width = w; s.dim.rect.height = h; return s;
}
Shape makeTri(char *lbl, double b, double h) {
    Shape s; s.type = TRIANGLE;
    snprintf(s.label, 15, "%s", lbl);
    s.dim.tri.base = b; s.dim.tri.height = h; return s;
}

/* Single area function for ALL shape types */
double area(const Shape *s) {
    switch (s->type) {
        case CIRCLE:   return 3.14159 * s->dim.circle.radius
                                        * s->dim.circle.radius;
        case RECT:     return s->dim.rect.width * s->dim.rect.height;
        case TRIANGLE: return 0.5 * s->dim.tri.base * s->dim.tri.height;
    }
    return 0;
}

void describe(const Shape *s) {
    const char *names[] = {"Circle", "Rectangle", "Triangle"};
    printf("%-14s %-10s area = %8.2f\n",
           s->label, names[s->type], area(s));
}

int main() {
    Shape scene[] = {
        makeCircle("Sun",          7.0),
        makeRect  ("Floor",       12.0, 8.0),
        makeTri   ("Roof",        10.0, 6.0),
        makeCircle("Wheel",        3.5),
        makeRect  ("Door",         2.0, 4.5),
        makeTri   ("Flag",         5.0, 3.0),
    };
    int n = sizeof(scene) / sizeof(scene[0]);

    printf("%-14s %-10s %s\n", "Label", "Type", "Area");
    printf("------------------------------------------\n");

    double total = 0;
    for (int i = 0; i < n; i++) {
        describe(&scene[i]);
        total += area(&scene[i]);
    }
    printf("------------------------------------------\n");
    printf("%-24s total = %8.2f\n", "", total);
    return 0;
}
output
Label          Type       Area
------------------------------------------
Sun            Circle     area =   153.94
Floor          Rectangle  area =    96.00
Roof           Triangle   area =    30.00
Wheel          Circle     area =    38.48
Door           Rectangle  area =     9.00
Flag           Triangle   area =     7.50
------------------------------------------
                           total =   334.92
All 4 union patterns in one program: makeCircle/Rect/Tri return a struct with embedded union · factory sets the tag + value together · area() dispatches on the tag · mixed-type array processed in a single clean loop. Add a new shape by adding one enum value, one struct in the union, one factory, and one case — nothing else changes.
checklist
  • Ex 1 — sizeof(union) = size of its largest member. All members share the same memory block.
  • Ex 2 — Only the last-written member is valid to read. Reading any other gives raw byte reinterpretation.
  • Ex 3 — Struct allocates memory for every field. Union allocates once for the largest — all start at the same address.
  • Ex 4 — Tagged union = union + enum tag inside a struct. Always check the tag before reading. Always set both tag and value together.
  • Ex 5 — union { uint; uchar[4]; } lets you read individual bytes of any integer. Reveals endianness of the machine.
  • Ex 6 — Type punning via union is legal in C. Writing float and reading uint gives raw IEEE 754 bits safely.
  • Ex 7 — Union nested inside a struct with an enum type field = variant record. Used in compilers, JSON parsers, GUI events.
  • Ex 8 — Bitfields + union = zero-shift structured access to raw bytes. Standard pattern in network drivers and hardware registers.
  • Ex 9 — Array of tagged unions = mixed-type container. How Python / Lua store all runtime variables internally in C.
  • Ex 10 — Factory + tag + single dispatch function + mixed array. Add a new variant by touching only 4 places — nothing else breaks.