Union Declaration

Introduction

Union declaration in C creates a user-defined data type that allows storing different data types in the same memory location. Unlike structures where each member has its own memory space, all members of a union share the same memory location. Only one member can hold a value at any given time, making unions useful for memory-efficient programming and type conversion.

Key Concepts

Union: User-defined type sharing memory among all members Memory Sharing: All members occupy the same memory location Size: Union size equals the size of its largest member Active Member: Only one member contains valid data at a time Type Punning: Interpreting same memory as different data types Variant Type: Union can represent multiple types in one variable

Basic Union Declaration

1. Simple Union Declaration

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

// Basic union declaration
union Data {
    int intValue;
    float floatValue;
    char charValue;
    char stringValue[20];
};

// Union with different data types
union Number {
    int integer;
    float decimal;
    double precision;
};

int main() {
    printf("=== Basic Union Declaration ===\n");
    
    union Data data;
    union Number num;
    
    // Store integer value
    data.intValue = 42;
    printf("Integer value: %d\n", data.intValue);
    printf("Union size: %zu bytes\n", sizeof(data));
    
    // Store float value (overwrites integer)
    data.floatValue = 3.14;
    printf("Float value: %.2f\n", data.floatValue);
    printf("Integer value now: %d (corrupted)\n", data.intValue);
    
    // Store character value
    data.charValue = 'A';
    printf("Character value: %c\n", data.charValue);
    
    // Store string value
    strcpy(data.stringValue, "Hello Union");
    printf("String value: %s\n", data.stringValue);
    
    // Demonstrate memory sharing
    printf("\nMemory addresses:\n");
    printf("intValue address: %p\n", (void*)&data.intValue);
    printf("floatValue address: %p\n", (void*)&data.floatValue);
    printf("charValue address: %p\n", (void*)&data.charValue);
    printf("stringValue address: %p\n", (void*)&data.stringValue);
    
    return 0;
}

2. Union vs Structure Size Comparison

#include <stdio.h>

// Structure with same members
struct DataStruct {
    int intValue;
    float floatValue;
    char charValue;
    char stringValue[20];
};

// Union with same members
union DataUnion {
    int intValue;
    float floatValue;
    char charValue;
    char stringValue[20];
};

int main() {
    printf("=== Union vs Structure Size ===\n");
    
    struct DataStruct ds;
    union DataUnion du;
    
    printf("Structure size: %zu bytes\n", sizeof(ds));
    printf("Union size: %zu bytes\n", sizeof(du));
    
    printf("\nStructure member sizes:\n");
    printf("int: %zu bytes\n", sizeof(ds.intValue));
    printf("float: %zu bytes\n", sizeof(ds.floatValue));
    printf("char: %zu bytes\n", sizeof(ds.charValue));
    printf("char[20]: %zu bytes\n", sizeof(ds.stringValue));
    
    printf("\nUnion member sizes (same as above):\n");
    printf("int: %zu bytes\n", sizeof(du.intValue));
    printf("float: %zu bytes\n", sizeof(du.floatValue));
    printf("char: %zu bytes\n", sizeof(du.charValue));
    printf("char[20]: %zu bytes\n", sizeof(du.stringValue));
    
    printf("\nMemory usage comparison:\n");
    printf("Structure total: %zu bytes (sum of all members + padding)\n", sizeof(ds));
    printf("Union total: %zu bytes (size of largest member)\n", sizeof(du));
    
    return 0;
}

Practical Union Applications

1. Type Conversion and Bit Manipulation

#include <stdio.h>

// Union for examining float representation
union FloatBits {
    float floatValue;
    unsigned int bits;
    struct {
        unsigned int mantissa : 23;
        unsigned int exponent : 8;
        unsigned int sign : 1;
    } parts;
};

// Union for byte access
union IntBytes {
    int value;
    unsigned char bytes[4];
};

// Union for endianness testing
union EndianTest {
    int value;
    char bytes[4];
};

void displayFloatBits(float f) {
    union FloatBits fb;
    fb.floatValue = f;
    
    printf("Float: %.6f\n", fb.floatValue);
    printf("Bits: 0x%08X\n", fb.bits);
    printf("Sign: %u, Exponent: %u, Mantissa: %u\n", 
           fb.parts.sign, fb.parts.exponent, fb.parts.mantissa);
}

void displayIntBytes(int value) {
    union IntBytes ib;
    ib.value = value;
    
    printf("Integer: %d (0x%08X)\n", ib.value, ib.value);
    printf("Bytes: ");
    for (int i = 0; i < 4; i++) {
        printf("0x%02X ", ib.bytes[i]);
    }
    printf("\n");
}

int main() {
    printf("=== Type Conversion and Bit Manipulation ===\n");
    
    // Float bit representation
    printf("Float bit representation:\n");
    displayFloatBits(3.14159f);
    displayFloatBits(-1.0f);
    displayFloatBits(0.0f);
    
    printf("\nInteger byte representation:\n");
    displayIntBytes(0x12345678);
    displayIntBytes(1000);
    
    // Endianness test
    union EndianTest et;
    et.value = 0x12345678;
    
    printf("\nEndianness test:\n");
    printf("Value: 0x%08X\n", et.value);
    printf("First byte: 0x%02X\n", (unsigned char)et.bytes[0]);
    
    if (et.bytes[0] == 0x78) {
        printf("System is Little Endian\n");
    } else if (et.bytes[0] == 0x12) {
        printf("System is Big Endian\n");
    }
    
    return 0;
}

2. Variant Data Types

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

// Enumeration for data types
typedef enum {
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING,
    TYPE_BOOL
} DataType;

// Variant structure using union
typedef struct {
    DataType type;
    union {
        int intValue;
        float floatValue;
        char stringValue[50];
        int boolValue;  // 0 for false, 1 for true
    } data;
} Variant;

// Function to create variant with integer
Variant createIntVariant(int value) {
    Variant v;
    v.type = TYPE_INT;
    v.data.intValue = value;
    return v;
}

// Function to create variant with float
Variant createFloatVariant(float value) {
    Variant v;
    v.type = TYPE_FLOAT;
    v.data.floatValue = value;
    return v;
}

// Function to create variant with string
Variant createStringVariant(const char* value) {
    Variant v;
    v.type = TYPE_STRING;
    strncpy(v.data.stringValue, value, sizeof(v.data.stringValue) - 1);
    v.data.stringValue[sizeof(v.data.stringValue) - 1] = '\0';
    return v;
}

// Function to create variant with boolean
Variant createBoolVariant(int value) {
    Variant v;
    v.type = TYPE_BOOL;
    v.data.boolValue = value ? 1 : 0;
    return v;
}

// Function to display variant
void displayVariant(Variant v) {
    switch (v.type) {
        case TYPE_INT:
            printf("Integer: %d\n", v.data.intValue);
            break;
        case TYPE_FLOAT:
            printf("Float: %.2f\n", v.data.floatValue);
            break;
        case TYPE_STRING:
            printf("String: %s\n", v.data.stringValue);
            break;
        case TYPE_BOOL:
            printf("Boolean: %s\n", v.data.boolValue ? "true" : "false");
            break;
        default:
            printf("Unknown type\n");
    }
}

int main() {
    printf("=== Variant Data Types ===\n");
    
    // Create array of variants
    Variant values[5];
    
    values[0] = createIntVariant(42);
    values[1] = createFloatVariant(3.14159);
    values[2] = createStringVariant("Hello, World!");
    values[3] = createBoolVariant(1);
    values[4] = createBoolVariant(0);
    
    // Display all variants
    printf("Variant array contents:\n");
    for (int i = 0; i < 5; i++) {
        printf("Value %d: ", i);
        displayVariant(values[i]);
    }
    
    // Demonstrate variant size efficiency
    printf("\nMemory usage:\n");
    printf("Variant size: %zu bytes\n", sizeof(Variant));
    printf("Individual sizes: int=%zu, float=%zu, string=%zu\n", 
           sizeof(int), sizeof(float), sizeof(char[50]));
    
    return 0;
}

3. Network Protocol Parsing

#include <stdio.h>
#include <stdint.h>

// IP address representation
union IPAddress {
    uint32_t address;
    struct {
        uint8_t octet1;
        uint8_t octet2;
        uint8_t octet3;
        uint8_t octet4;
    } octets;
    uint8_t bytes[4];
};

// Network packet header
union PacketHeader {
    uint32_t raw;
    struct {
        uint32_t version : 4;
        uint32_t headerLength : 4;
        uint32_t typeOfService : 8;
        uint32_t totalLength : 16;
    } fields;
};

// Color representation
union Color {
    uint32_t value;
    struct {
        uint8_t blue;
        uint8_t green;
        uint8_t red;
        uint8_t alpha;
    } rgba;
    uint8_t components[4];
};

void displayIPAddress(union IPAddress ip) {
    printf("IP Address: %u.%u.%u.%u\n", 
           ip.octets.octet1, ip.octets.octet2, 
           ip.octets.octet3, ip.octets.octet4);
    printf("Raw value: 0x%08X\n", ip.address);
    printf("Bytes: %u %u %u %u\n", 
           ip.bytes[0], ip.bytes[1], ip.bytes[2], ip.bytes[3]);
}

void displayPacketHeader(union PacketHeader header) {
    printf("Packet Header: 0x%08X\n", header.raw);
    printf("Version: %u\n", header.fields.version);
    printf("Header Length: %u\n", header.fields.headerLength);
    printf("Type of Service: %u\n", header.fields.typeOfService);
    printf("Total Length: %u\n", header.fields.totalLength);
}

void displayColor(union Color color) {
    printf("Color: 0x%08X\n", color.value);
    printf("RGBA: (%u, %u, %u, %u)\n", 
           color.rgba.red, color.rgba.green, color.rgba.blue, color.rgba.alpha);
    printf("Components: [%u, %u, %u, %u]\n", 
           color.components[0], color.components[1], 
           color.components[2], color.components[3]);
}

int main() {
    printf("=== Network Protocol Parsing ===\n");
    
    // IP address example
    union IPAddress ip;
    ip.address = 0xC0A80101;  // 192.168.1.1
    
    printf("IP Address Analysis:\n");
    displayIPAddress(ip);
    
    // Set IP by octets
    ip.octets.octet1 = 10;
    ip.octets.octet2 = 0;
    ip.octets.octet3 = 0;
    ip.octets.octet4 = 1;
    
    printf("\nModified IP Address:\n");
    displayIPAddress(ip);
    
    // Packet header example
    union PacketHeader header;
    header.raw = 0x45000028;  // IPv4 header example
    
    printf("\nPacket Header Analysis:\n");
    displayPacketHeader(header);
    
    // Color example
    union Color color;
    color.value = 0xFF00FF80;  // Purple with transparency
    
    printf("\nColor Analysis:\n");
    displayColor(color);
    
    return 0;
}

Union with typedef

Simplifying Union Usage

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

// Union with typedef for cleaner syntax
typedef union {
    int asInt;
    float asFloat;
    char asChar;
    unsigned int asUnsigned;
} Value;

// Tagged union for safe type handling
typedef enum {
    TAG_INT,
    TAG_FLOAT,
    TAG_STRING
} ValueTag;

typedef struct {
    ValueTag tag;
    union {
        int intVal;
        float floatVal;
        char stringVal[32];
    } value;
} TaggedValue;

// Function to create tagged integer
TaggedValue makeInt(int val) {
    TaggedValue tv;
    tv.tag = TAG_INT;
    tv.value.intVal = val;
    return tv;
}

// Function to create tagged float
TaggedValue makeFloat(float val) {
    TaggedValue tv;
    tv.tag = TAG_FLOAT;
    tv.value.floatVal = val;
    return tv;
}

// Function to create tagged string
TaggedValue makeString(const char* val) {
    TaggedValue tv;
    tv.tag = TAG_STRING;
    strncpy(tv.value.stringVal, val, sizeof(tv.value.stringVal) - 1);
    tv.value.stringVal[sizeof(tv.value.stringVal) - 1] = '\0';
    return tv;
}

// Function to safely print tagged value
void printTaggedValue(TaggedValue tv) {
    switch (tv.tag) {
        case TAG_INT:
            printf("Integer: %d\n", tv.value.intVal);
            break;
        case TAG_FLOAT:
            printf("Float: %.2f\n", tv.value.floatVal);
            break;
        case TAG_STRING:
            printf("String: %s\n", tv.value.stringVal);
            break;
        default:
            printf("Unknown type\n");
    }
}

int main() {
    printf("=== Union with typedef ===\n");
    
    // Simple union usage
    Value val;
    
    val.asInt = 42;
    printf("As integer: %d\n", val.asInt);
    printf("As float: %.2f\n", val.asFloat);  // Interpretation, not conversion
    printf("As unsigned: %u\n", val.asUnsigned);
    
    val.asFloat = 3.14159f;
    printf("\nAfter setting float:\n");
    printf("As integer: %d\n", val.asInt);      // Now invalid
    printf("As float: %.2f\n", val.asFloat);    // Valid
    
    // Tagged union usage
    printf("\nTagged Union (Safe):\n");
    TaggedValue values[3];
    
    values[0] = makeInt(100);
    values[1] = makeFloat(2.71828f);
    values[2] = makeString("Hello");
    
    for (int i = 0; i < 3; i++) {
        printf("Value %d: ", i);
        printTaggedValue(values[i]);
    }
    
    return 0;
}

Memory Layout and Alignment

Understanding Union Memory Layout

#include <stdio.h>
#include <stddef.h>

// Union with different sized members
union MixedUnion {
    char c;
    short s;
    int i;
    long l;
    float f;
    double d;
};

// Union with structure member
struct Point {
    int x, y;
};

union ShapeData {
    int radius;              // For circle
    struct Point dimensions; // For rectangle
    float sideLength;        // For square
};

// Union with array
union ArrayUnion {
    int singleInt;
    int intArray[4];
    char charArray[16];
};

int main() {
    printf("=== Union Memory Layout ===\n");
    
    union MixedUnion mu;
    union ShapeData sd;
    union ArrayUnion au;
    
    // Display union sizes
    printf("Union sizes:\n");
    printf("MixedUnion: %zu bytes\n", sizeof(mu));
    printf("ShapeData: %zu bytes\n", sizeof(sd));
    printf("ArrayUnion: %zu bytes\n", sizeof(au));
    
    // Display member sizes
    printf("\nMixedUnion member sizes:\n");
    printf("char: %zu bytes\n", sizeof(mu.c));
    printf("short: %zu bytes\n", sizeof(mu.s));
    printf("int: %zu bytes\n", sizeof(mu.i));
    printf("long: %zu bytes\n", sizeof(mu.l));
    printf("float: %zu bytes\n", sizeof(mu.f));
    printf("double: %zu bytes\n", sizeof(mu.d));
    
    // Show memory addresses are the same
    printf("\nMixedUnion member addresses:\n");
    printf("char address: %p\n", (void*)&mu.c);
    printf("short address: %p\n", (void*)&mu.s);
    printf("int address: %p\n", (void*)&mu.i);
    printf("double address: %p\n", (void*)&mu.d);
    
    // Demonstrate overlapping memory
    au.singleInt = 0x12345678;
    printf("\nArray union demonstration:\n");
    printf("Single int: 0x%08X\n", au.singleInt);
    printf("Int array: ");
    for (int i = 0; i < 4; i++) {
        printf("0x%08X ", au.intArray[i]);
    }
    printf("\n");
    
    printf("Char array: ");
    for (int i = 0; i < 16; i++) {
        printf("%02X ", (unsigned char)au.charArray[i]);
    }
    printf("\n");
    
    return 0;
}

Union Safety and Best Practices

Safe Union Usage Patterns

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

// Enumeration for union member types
typedef enum {
    DATA_EMPTY,
    DATA_INTEGER,
    DATA_FLOAT,
    DATA_STRING
} DataType;

// Safe union with type tracking
typedef struct {
    DataType type;
    union {
        int intValue;
        float floatValue;
        char stringValue[50];
    } data;
} SafeUnion;

// Function to safely set integer
void setInteger(SafeUnion* su, int value) {
    su->type = DATA_INTEGER;
    su->data.intValue = value;
}

// Function to safely set float
void setFloat(SafeUnion* su, float value) {
    su->type = DATA_FLOAT;
    su->data.floatValue = value;
}

// Function to safely set string
void setString(SafeUnion* su, const char* value) {
    su->type = DATA_STRING;
    strncpy(su->data.stringValue, value, sizeof(su->data.stringValue) - 1);
    su->data.stringValue[sizeof(su->data.stringValue) - 1] = '\0';
}

// Function to safely get integer
int getInteger(const SafeUnion* su, int* result) {
    if (su->type == DATA_INTEGER) {
        *result = su->data.intValue;
        return 1;  // Success
    }
    return 0;  // Wrong type
}

// Function to safely get float
int getFloat(const SafeUnion* su, float* result) {
    if (su->type == DATA_FLOAT) {
        *result = su->data.floatValue;
        return 1;  // Success
    }
    return 0;  // Wrong type
}

// Function to safely get string
int getString(const SafeUnion* su, char* result, size_t maxSize) {
    if (su->type == DATA_STRING) {
        strncpy(result, su->data.stringValue, maxSize - 1);
        result[maxSize - 1] = '\0';
        return 1;  // Success
    }
    return 0;  // Wrong type
}

// Function to display safe union
void displaySafeUnion(const SafeUnion* su) {
    switch (su->type) {
        case DATA_EMPTY:
            printf("Empty union\n");
            break;
        case DATA_INTEGER:
            printf("Integer: %d\n", su->data.intValue);
            break;
        case DATA_FLOAT:
            printf("Float: %.2f\n", su->data.floatValue);
            break;
        case DATA_STRING:
            printf("String: %s\n", su->data.stringValue);
            break;
        default:
            printf("Unknown type\n");
    }
}

int main() {
    printf("=== Safe Union Usage ===\n");
    
    SafeUnion su = {DATA_EMPTY, {0}};  // Initialize empty
    
    // Safely set and access values
    setInteger(&su, 42);
    displaySafeUnion(&su);
    
    setFloat(&su, 3.14159f);
    displaySafeUnion(&su);
    
    setString(&su, "Hello, Safe Union!");
    displaySafeUnion(&su);
    
    // Demonstrate safe access
    int intResult;
    float floatResult;
    char stringResult[100];
    
    printf("\nSafe access attempts:\n");
    
    if (getInteger(&su, &intResult)) {
        printf("Got integer: %d\n", intResult);
    } else {
        printf("Cannot get integer (wrong type)\n");
    }
    
    if (getFloat(&su, &floatResult)) {
        printf("Got float: %.2f\n", floatResult);
    } else {
        printf("Cannot get float (wrong type)\n");
    }
    
    if (getString(&su, stringResult, sizeof(stringResult))) {
        printf("Got string: %s\n", stringResult);
    } else {
        printf("Cannot get string (wrong type)\n");
    }
    
    return 0;
}

Important Points

  1. Memory sharing: All union members occupy the same memory location
  2. Size efficiency: Union size equals largest member size
  3. One active member: Only one member contains valid data at a time
  4. Type safety: Programmer responsible for tracking active member
  5. Bit patterns: Members can reinterpret same bit pattern differently
  6. Alignment: Union aligned to most restrictive member requirement
  7. Initialization: Only first member can be initialized in declaration
  8. Portability: Behavior may vary across different systems

Best Practices

  1. Track active member using separate type indicator
  2. Use tagged unions for type safety
  3. Initialize unions explicitly after declaration
  4. Avoid accessing inactive members
  5. Document intended usage for each union
  6. Consider alternatives like void pointers or inheritance
  7. Test on target platform for portability
  8. Use unions sparingly and only when memory efficiency is critical

Summary

Union declaration in C creates memory-efficient data types where all members share the same memory location. Unions are useful for type conversion, variant data types, and memory-constrained applications. However, they require careful handling to maintain type safety and avoid accessing invalid data. Tagged unions provide a safer approach by tracking the active member type.


Part of BCA Programming with C Course (UGCOA22J201)