Unions · learncode.live

Unions

A union is a user-defined data type in C that allows you to store different data types in the same memory location. Unlike a struct where each member has its own memory, all members of a union share the same memory space. At any given time, only one member can hold a value.

Think of a union like a parking spot - you can park a car, a bike, or a truck there, but only one vehicle at a time. A struct is like a multi-story parking garage where each vehicle gets its own spot.

union Data {
    int i;
    float f;
    char str[20];
};

Declaring a Union

The syntax is similar to a struct, but the behavior is very different.

#include <stdio.h>
#include <string.h>

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;

    printf("Size of union: %zu bytes\n", sizeof(data));
    // Size is 20 (largest member: str[20]), not 4 + 4 + 20 = 28

    return 0;
}

A union’s size is determined by its largest member. All members share the same starting memory address.

Union vs Struct - Memory Comparison

#include <stdio.h>

struct StructData {
    int i;
    float f;
    char str[20];
};

union UnionData {
    int i;
    float f;
    char str[20];
};

int main() {
    printf("Struct size: %zu bytes\n", sizeof(struct StructData));
    printf("Union size:  %zu bytes\n", sizeof(union UnionData));

    return 0;
}

Output (on a typical 64-bit system):

Struct size: 28 bytes
Union size:  20 bytes

The struct allocates space for all members (with padding). The union allocates space only for the largest member - all members overlap in memory.

Accessing Union Members

Use the dot operator (.) just like a struct. But remember: writing to one member overwrites the others.

#include <stdio.h>
#include <string.h>

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;

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

    data.f = 3.14;          // Overwrites data.i
    printf("data.f: %.2f\n", data.f);

    strcpy(data.str, "hello");  // Overwrites data.f
    printf("data.str: %s\n", data.str);

    // ⚠️ data.i and data.f are now garbage
    printf("data.i after str: %d\n", data.i);  // Unpredictable

    return 0;
}

Only the last written member contains valid data. Reading any other member gives garbage.

When to Use Unions

Unions are useful when you need to store one of several possible types but only one at a time. A common pattern is a tagged union - pairing a union with an enum to track which member is active.

#include <stdio.h>
#include <string.h>

enum DataType { TYPE_INT, TYPE_FLOAT, TYPE_STRING };

struct TaggedValue {
    enum DataType type;
    union {
        int i;
        float f;
        char str[50];
    } value;
};

void print_value(struct TaggedValue tv) {
    switch (tv.type) {
        case TYPE_INT:
            printf("Integer: %d\n", tv.value.i);
            break;
        case TYPE_FLOAT:
            printf("Float: %.2f\n", tv.value.f);
            break;
        case TYPE_STRING:
            printf("String: %s\n", tv.value.str);
            break;
    }
}

int main() {
    struct TaggedValue v1 = { TYPE_INT, .value.i = 100 };
    struct TaggedValue v2 = { TYPE_FLOAT, .value.f = 99.99 };
    struct TaggedValue v3 = { TYPE_STRING };
    strcpy(v3.value.str, "Hello, Union!");

    print_value(v1);
    print_value(v2);
    print_value(v3);

    return 0;
}

This is the foundation of how many dynamic typing systems work - even in languages like Python or JavaScript, under the hood.

Arrays of Unions

You can create an array of unions where each element holds a different type:

#include <stdio.h>

union Value {
    int i;
    float f;
};

int main() {
    union Value arr[3];

    arr[0].i = 10;
    arr[1].f = 3.14;
    arr[2].i = -5;

    for (int i = 0; i < 3; i++) {
        printf("arr[%d] as int: %d\n", i, arr[i].i);
    }

    return 0;
}

⚠️ Again, interpret the data as the correct type based on your own tracking.

Union Inside a Struct

Unions are often embedded inside structs to save memory when a field has mutually exclusive options.

#include <stdio.h>

struct Employee {
    int id;
    char name[50];
    int role;  // 0 = hourly, 1 = salaried
    union {
        double hourly_wage;
        double annual_salary;
    } pay;
};

int main() {
    struct Employee e1 = {1, "Alice", 0, .pay.hourly_wage = 25.50};
    struct Employee e2 = {2, "Bob", 1, .pay.annual_salary = 75000.0};

    printf("%s earns $%.2f per hour\n", e1.name, e1.pay.hourly_wage);
    printf("%s earns $%.2f per year\n", e2.name, e2.pay.annual_salary);

    return 0;
}

This is more memory-efficient than using a struct with both hourly_wage and annual_salary as separate members, since an employee only ever has one type of pay.

Key Differences: struct vs union

Featurestructunion
MemoryAllocates memory for all membersAllocates memory for largest member
Member accessAll members valid simultaneouslyOnly one member valid at a time
Use caseGroup related dataStore one of several possible types
SizeSum of all members + paddingSize of largest member
InitializationCan initialize all membersCan only initialize first member
Courses