Bootstrap demo

Unions in C Language

Complete Guide with Examples and Best Practices

Table of Contents

1. Introduction to Unions

A union in C is a special data type that allows storing different data types in the same memory location. Unlike structures, where each member has its own memory location, union members share the same memory location.

Key Point: The size of a union is determined by the size of its largest member. Only one member can contain a value at any given time.

Unions are useful when you need to use the same memory location for different purposes or when you want to save memory by reusing the same memory space for different types of data.

2. Union Definition and Declaration

2.1 Union Syntax

Definition: The union keyword is used to define a union.

Syntax:

union union_name {
    data_type member1;
    data_type member2;
    // ... more members
};

Example:

// Define a union that can store different data types
union Data {
    int i;
    float f;
    char str[20];
};

2.2 Union Declaration

After defining a union, you can declare variables of that union type.

Example:

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

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

int main() {
    // Declare a union variable
    union Data data;
    
    return 0;
}

2.3 Union Initialization

Union variables can be initialized, but only the first member can be initialized directly.

Example:

#include <stdio.h>

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

int main() {
    // Initialize the first member of the union
    union Data data = {10};
    
    printf("data.i = %d\n", data.i);
    
    return 0;
}

Output:

data.i = 10

3. Accessing Union Members

Union members are accessed using the dot (.) operator. However, only one member should be accessed at a time as they share the same memory location.

Example:

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

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

int main() {
    union Data data;
    
    // Store integer
    data.i = 10;
    printf("data.i : %d\n", data.i);
    
    // Store float - this will corrupt the previous value
    data.f = 220.5;
    printf("data.f : %.2f\n", data.f);
    
    // Store string - this will corrupt the previous value
    strcpy(data.str, "C Programming");
    printf("data.str : %s\n", data.str);
    
    // Demonstrate that only one value is stored at a time
    printf("data.i after storing string: %d (garbage value)\n", data.i);
    printf("data.f after storing string: %.2f (garbage value)\n", data.f);
    
    return 0;
}

Output:

data.i : 10
data.f : 220.50
data.str : C Programming
data.i after storing string: 1917853763 (garbage value)
data.f after storing string: 4122360580327794900000000000000.00 (garbage value)

4. Memory Allocation in Unions

Unions allocate memory equal to the size of their largest member. All members share this same memory space.

Example:

#include <stdio.h>

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

int main() {
    union Data data;
    
    printf("Memory size occupied by data : %lu bytes\n", sizeof(data));
    printf("Size of int: %lu bytes\n", sizeof(int));
    printf("Size of float: %lu bytes\n", sizeof(float));
    printf("Size of char[20]: %lu bytes\n", sizeof(char[20]));
    
    return 0;
}

Output:

Memory size occupied by data : 20 bytes
Size of int: 4 bytes
Size of float: 4 bytes
Size of char[20]: 20 bytes

Memory Visualization:

For the union Data defined above, the memory allocation would look like this:

Memory Address    Member    Value (example)
0x1000 - 0x1003   i         10 (when storing integer)
0x1000 - 0x1003   f         220.5 (when storing float)
0x1000 - 0x1013   str       "C Programming" (when storing string)
                

All members share the same starting memory address (0x1000 in this example).

5. Union vs Structure

Feature Union Structure
Keyword union struct
Memory Allocation Members share memory Each member has separate memory
Total Size Size of largest member Sum of sizes of all members (plus padding)
Member Access Only one member can be accessed at a time All members can be accessed independently
Memory Efficiency High (memory is reused) Low (each member uses separate memory)
Use Case When only one member is needed at a time When all members need to be stored simultaneously
#include <stdio.h>

// Define a union and a structure with same members
union UnionData {
    int i;
    float f;
    char str[20];
};

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

int main() {
    union UnionData u;
    struct StructData s;
    
    printf("Size of union: %lu bytes\n", sizeof(u));
    printf("Size of structure: %lu bytes\n", sizeof(s));
    
    return 0;
}

Output:

Size of union: 20 bytes
Size of structure: 28 bytes

6. typedef with Unions

The typedef keyword can be used with unions to create an alias, making the code cleaner and easier to read.

Example:

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

// Using typedef to create an alias for union
typedef union {
    int i;
    float f;
    char str[20];
} Data;

int main() {
    // Now we can use Data instead of union Data
    Data data;
    
    data.i = 10;
    printf("data.i : %d\n", data.i);
    
    data.f = 220.5;
    printf("data.f : %.2f\n", data.f);
    
    return 0;
}

Note: Using typedef with unions makes the code cleaner and easier to read, as you don't have to use the union keyword every time you declare a variable.

7. Practical Applications of Unions

7.1 Variant Data Types

Unions are often used to implement variant data types where a value can be one of several types.

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

typedef enum { INT, FLOAT, STRING } Type;

typedef struct {
    Type type;
    union {
        int i;
        float f;
        char str[20];
    } value;
} Variant;

void printVariant(Variant v) {
    switch(v.type) {
        case INT:
            printf("Integer: %d\n", v.value.i);
            break;
        case FLOAT:
            printf("Float: %.2f\n", v.value.f);
            break;
        case STRING:
            printf("String: %s\n", v.value.str);
            break;
    }
}

int main() {
    Variant v1 = {INT, .value.i = 10};
    Variant v2 = {FLOAT, .value.f = 3.14};
    Variant v3 = {STRING, .value.str = "Hello"};
    
    printVariant(v1);
    printVariant(v2);
    printVariant(v3);
    
    return 0;
}

7.2 Hardware Register Access

Unions are useful for accessing hardware registers that can be interpreted in different ways.

#include <stdio.h>

// Example: A 32-bit register that can be accessed as a whole or as bytes
typedef union {
    unsigned int value;
    struct {
        unsigned char byte0;
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
    } bytes;
} HardwareRegister;

int main() {
    HardwareRegister reg;
    reg.value = 0x12345678;
    
    printf("Full register value: 0x%08X\n", reg.value);
    printf("Byte 0: 0x%02X\n", reg.bytes.byte0);
    printf("Byte 1: 0x%02X\n", reg.bytes.byte1);
    printf("Byte 2: 0x%02X\n", reg.bytes.byte2);
    printf("Byte 3: 0x%02X\n", reg.bytes.byte3);
    
    return 0;
}

8. Best Practices

  • Use unions only when necessary - they can make code harder to understand
  • Keep track of the active member - use a separate variable (like an enum) to track which member is currently valid
  • Document union usage clearly - comment your code to explain why a union is being used
  • Use typedef with unions to make code cleaner and easier to read
  • Be cautious with pointer aliasing - accessing a union through different member types may violate strict aliasing rules
  • Consider memory alignment issues - some architectures have alignment requirements

9. Common Mistakes

1. Accessing the wrong member

union Data {
    int i;
    float f;
};

union Data data;
data.i = 10;

// Wrong: Accessing f after storing i
printf("%f", data.f); // This will print garbage

2. Forgetting to track the active member

union Data {
    int i;
    float f;
};

union Data data;
data.i = 10;
// ... later in code ...
// How do we know if data.i or data.f contains valid data?

3. Assuming all members can be accessed simultaneously

union Data {
    int i;
    float f;
};

union Data data;
data.i = 10;
data.f = 3.14; // This overwrites the integer value

// Wrong: Assuming both values are stored
printf("%d and %f", data.i, data.f); // data.i is now garbage

10. Real-world Examples

Example 1: Network Data Packet

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

// Different types of network packets
typedef enum { INT_DATA, FLOAT_DATA, TEXT_DATA } PacketType;

typedef struct {
    PacketType type;
    union {
        int intValue;
        float floatValue;
        char textValue[100];
    } data;
} NetworkPacket;

void processPacket(NetworkPacket packet) {
    switch(packet.type) {
        case INT_DATA:
            printf("Received integer: %d\n", packet.data.intValue);
            break;
        case FLOAT_DATA:
            printf("Received float: %.2f\n", packet.data.floatValue);
            break;
        case TEXT_DATA:
            printf("Received text: %s\n", packet.data.textValue);
            break;
    }
}

int main() {
    NetworkPacket packets[3];
    
    packets[0].type = INT_DATA;
    packets[0].data.intValue = 42;
    
    packets[1].type = FLOAT_DATA;
    packets[1].data.floatValue = 3.14159;
    
    packets[2].type = TEXT_DATA;
    strcpy(packets[2].data.textValue, "Hello, Network!");
    
    for(int i = 0; i < 3; i++) {
        processPacket(packets[i]);
    }
    
    return 0;
}

Example 2: Mixed Type Array

#include <stdio.h>
#include <stdlib.h>

typedef enum { INT, FLOAT, CHAR } VarType;

typedef struct {
    VarType type;
    union {
        int i;
        float f;
        char c;
    } value;
} Variant;

void printVariant(Variant v) {
    switch(v.type) {
        case INT:
            printf("Integer: %d\n", v.value.i);
            break;
        case FLOAT:
            printf("Float: %.2f\n", v.value.f);
            break;
        case CHAR:
            printf("Char: %c\n", v.value.c);
  ⊤          break;
    }
}

int main() {
    Variant arr[5];
    
    arr[0].type = INT;
    arr[0].value.i = 10;
    
    arr[1].type = FLOAT;
    arr[1].value.f = 3.14;
    
    arr[2].type = CHAR;
    arr[2].value.c = 'A';
    
    arr[3].type = INT;
    arr[3].value.i = 20;
    
    arr[4].type = FLOAT;
    arr[4].value.f = 2.718;
    
    for(int i = 0; i < 5; i++) {
        printVariant(arr[i]);
    }
    
    return 0;
}