Accessing Union Elements

Introduction

Accessing union elements in C involves using the same syntax as structures, but with different behavior. Unlike structures where all members have separate memory locations, union members share the same memory space. Understanding how to properly access and manipulate union members is crucial for efficient memory usage and data type conversions.

Key Concepts

Memory Sharing: All union members occupy the same memory location Dot Operator (.): Used to access union members directly Arrow Operator (->): Used to access union members through pointers Last Assignment Rule: Only the last assigned member contains valid data Type Punning: Using unions for data type interpretation

Basic Union Access

Simple Union Declaration and Access

#include <stdio.h>

union Data {
    int integer;
    float decimal;
    char character;
};

int main() {
    union Data data;
    
    // Access integer member
    data.integer = 42;
    printf("Integer value: %d\n", data.integer);
    printf("Memory address: %p\n", &data.integer);
    
    // Access float member (overwrites integer)
    data.decimal = 3.14;
    printf("Float value: %.2f\n", data.decimal);
    printf("Memory address: %p\n", &data.decimal);
    
    // Access char member (overwrites previous data)
    data.character = 'A';
    printf("Character value: %c\n", data.character);
    printf("Memory address: %p\n", &data.character);
    
    // Demonstrate memory sharing
    printf("\nMemory sharing demonstration:\n");
    printf("Union size: %zu bytes\n", sizeof(union Data));
    printf("Integer member size: %zu bytes\n", sizeof(data.integer));
    printf("Float member size: %zu bytes\n", sizeof(data.decimal));
    printf("Char member size: %zu bytes\n", sizeof(data.character));
    
    return 0;
}

Observing Data Corruption

#include <stdio.h>

union Example {
    int number;
    char bytes[4];
    float value;
};

int main() {
    union Example ex;
    
    // Set integer value
    ex.number = 0x12345678;
    printf("Integer: 0x%X (%d)\n", ex.number, ex.number);
    
    // Access same memory as char array
    printf("Bytes: ");
    for (int i = 0; i < 4; i++) {
        printf("0x%02X ", (unsigned char)ex.bytes[i]);
    }
    printf("\n");
    
    // Overwrite with float
    ex.value = 123.456f;
    printf("Float: %.3f\n", ex.value);
    
    // Now integer contains garbage
    printf("Integer after float assignment: %d\n", ex.number);
    
    return 0;
}

Pointer-Based Access

Using Pointers with Unions

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

union Storage {
    int intVal;
    float floatVal;
    char stringVal[20];
};

void displayUnion(union Storage *ptr, char type) {
    switch (type) {
        case 'i':
            printf("Integer value: %d\n", ptr->intVal);
            break;
        case 'f':
            printf("Float value: %.2f\n", ptr->floatVal);
            break;
        case 's':
            printf("String value: %s\n", ptr->stringVal);
            break;
        default:
            printf("Unknown type\n");
    }
}

int main() {
    union Storage storage;
    union Storage *ptr = &storage;
    
    // Access through pointer using arrow operator
    ptr->intVal = 100;
    displayUnion(ptr, 'i');
    
    ptr->floatVal = 98.6;
    displayUnion(ptr, 'f');
    
    strcpy(ptr->stringVal, "Hello Union");
    displayUnion(ptr, 's');
    
    // Alternative access using dereference
    (*ptr).intVal = 200;
    printf("Using dereference: %d\n", (*ptr).intVal);
    
    return 0;
}

Dynamic Union Allocation

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

union DataType {
    int integer;
    double real;
    char text[50];
};

int main() {
    // Allocate memory for union
    union DataType *dataPtr = (union DataType*)malloc(sizeof(union DataType));
    
    if (dataPtr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }
    
    // Initialize and access through pointer
    dataPtr->integer = 42;
    printf("Integer: %d\n", dataPtr->integer);
    
    dataPtr->real = 3.14159;
    printf("Real: %.5f\n", dataPtr->real);
    
    strcpy(dataPtr->text, "Dynamic Union");
    printf("Text: %s\n", dataPtr->text);
    
    // Check what happened to integer after string assignment
    printf("Integer after string: %d\n", dataPtr->integer);
    
    free(dataPtr);
    return 0;
}

Practical Applications

Type Conversion Union

#include <stdio.h>

union TypeConverter {
    float floatVal;
    unsigned int intBits;
    struct {
        unsigned int mantissa : 23;
        unsigned int exponent : 8;
        unsigned int sign : 1;
    } ieee754;
};

void analyzeFloat(float value) {
    union TypeConverter converter;
    converter.floatVal = value;
    
    printf("Float value: %.6f\n", converter.floatVal);
    printf("Integer representation: 0x%08X\n", converter.intBits);
    printf("IEEE 754 breakdown:\n");
    printf("  Sign: %u\n", converter.ieee754.sign);
    printf("  Exponent: %u (biased)\n", converter.ieee754.exponent);
    printf("  Mantissa: 0x%06X\n", converter.ieee754.mantissa);
    printf("---\n");
}

int main() {
    printf("IEEE 754 Float Analysis:\n");
    analyzeFloat(1.0f);
    analyzeFloat(-1.0f);
    analyzeFloat(3.14159f);
    analyzeFloat(0.0f);
    
    return 0;
}

Network Packet Union

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

union NetworkPacket {
    struct {
        unsigned char header[8];
        unsigned char payload[56];
    } structured;
    
    unsigned char raw[64];
    
    struct {
        unsigned int sourceIP;
        unsigned int destIP;
        unsigned short sourcePort;
        unsigned short destPort;
        unsigned char data[56];
    } network;
};

void displayPacket(union NetworkPacket *packet) {
    printf("Network Packet Analysis:\n");
    printf("Source IP: 0x%08X\n", packet->network.sourceIP);
    printf("Dest IP: 0x%08X\n", packet->network.destIP);
    printf("Source Port: %u\n", packet->network.sourcePort);
    printf("Dest Port: %u\n", packet->network.destPort);
    
    printf("Raw bytes (first 16): ");
    for (int i = 0; i < 16; i++) {
        printf("%02X ", packet->raw[i]);
    }
    printf("\n");
    
    printf("Header: ");
    for (int i = 0; i < 8; i++) {
        printf("%02X ", packet->structured.header[i]);
    }
    printf("\n---\n");
}

int main() {
    union NetworkPacket packet;
    
    // Initialize network fields
    packet.network.sourceIP = 0xC0A80001;    // 192.168.0.1
    packet.network.destIP = 0xC0A80002;      // 192.168.0.2
    packet.network.sourcePort = 8080;
    packet.network.destPort = 80;
    
    // Fill some data
    strcpy((char*)packet.network.data, "HTTP Request Data");
    
    displayPacket(&packet);
    
    return 0;
}

Configuration Union

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

union ConfigValue {
    int integerConfig;
    float floatConfig;
    char stringConfig[32];
    struct {
        unsigned char red;
        unsigned char green;
        unsigned char blue;
        unsigned char alpha;
    } colorConfig;
};

typedef struct {
    char name[20];
    char type;  // 'i' = int, 'f' = float, 's' = string, 'c' = color
    union ConfigValue value;
} Configuration;

void displayConfig(Configuration *config) {
    printf("Config: %s = ", config->name);
    
    switch (config->type) {
        case 'i':
            printf("%d (integer)\n", config->value.integerConfig);
            break;
        case 'f':
            printf("%.2f (float)\n", config->value.floatConfig);
            break;
        case 's':
            printf("\"%s\" (string)\n", config->value.stringConfig);
            break;
        case 'c':
            printf("RGBA(%d, %d, %d, %d) (color)\n",
                   config->value.colorConfig.red,
                   config->value.colorConfig.green,
                   config->value.colorConfig.blue,
                   config->value.colorConfig.alpha);
            break;
        default:
            printf("Unknown type\n");
    }
}

int main() {
    Configuration configs[4];
    
    // Integer config
    strcpy(configs[0].name, "MaxConnections");
    configs[0].type = 'i';
    configs[0].value.integerConfig = 100;
    
    // Float config
    strcpy(configs[1].name, "Timeout");
    configs[1].type = 'f';
    configs[1].value.floatConfig = 30.5;
    
    // String config
    strcpy(configs[2].name, "ServerName");
    configs[2].type = 's';
    strcpy(configs[2].value.stringConfig, "WebServer");
    
    // Color config
    strcpy(configs[3].name, "ThemeColor");
    configs[3].type = 'c';
    configs[3].value.colorConfig.red = 255;
    configs[3].value.colorConfig.green = 128;
    configs[3].value.colorConfig.blue = 0;
    configs[3].value.colorConfig.alpha = 255;
    
    printf("Application Configuration:\n");
    for (int i = 0; i < 4; i++) {
        displayConfig(&configs[i]);
    }
    
    return 0;
}

Array of Unions

Managing Multiple Union Elements

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

union VariantData {
    int intVal;
    double doubleVal;
    char stringVal[20];
};

typedef struct {
    char type;  // 'i', 'd', 's'
    union VariantData data;
} Variant;

void printVariant(Variant *v) {
    switch (v->type) {
        case 'i':
            printf("Integer: %d\n", v->data.intVal);
            break;
        case 'd':
            printf("Double: %.2f\n", v->data.doubleVal);
            break;
        case 's':
            printf("String: %s\n", v->data.stringVal);
            break;
        default:
            printf("Unknown type\n");
    }
}

int main() {
    Variant variants[5];
    
    // Initialize array of variants
    variants[0].type = 'i';
    variants[0].data.intVal = 42;
    
    variants[1].type = 'd';
    variants[1].data.doubleVal = 3.14159;
    
    variants[2].type = 's';
    strcpy(variants[2].data.stringVal, "Hello");
    
    variants[3].type = 'i';
    variants[3].data.intVal = -100;
    
    variants[4].type = 's';
    strcpy(variants[4].data.stringVal, "World");
    
    printf("Variant Array Contents:\n");
    for (int i = 0; i < 5; i++) {
        printf("%d. ", i + 1);
        printVariant(&variants[i]);
    }
    
    return 0;
}

Functions with Unions

Passing Unions to Functions

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

union Number {
    int integerPart;
    float floatingPart;
};

// Function receiving union by value
void processNumber(union Number num, char type) {
    printf("Processing number (by value): ");
    if (type == 'i') {
        printf("Integer = %d\n", num.integerPart);
    } else if (type == 'f') {
        printf("Float = %.2f\n", num.floatingPart);
    }
}

// Function receiving union by reference
void modifyNumber(union Number *num, char type, double value) {
    if (type == 'i') {
        num->integerPart = (int)value;
        printf("Set integer to %d\n", num->integerPart);
    } else if (type == 'f') {
        num->floatingPart = (float)value;
        printf("Set float to %.2f\n", num->floatingPart);
    }
}

// Function returning a union
union Number createNumber(char type, double value) {
    union Number result;
    
    if (type == 'i') {
        result.integerPart = (int)value;
    } else {
        result.floatingPart = (float)value;
    }
    
    return result;
}

int main() {
    union Number num1, num2;
    
    // Create unions using function
    num1 = createNumber('i', 123.7);
    num2 = createNumber('f', 456.89);
    
    // Process unions
    processNumber(num1, 'i');
    processNumber(num2, 'f');
    
    // Modify unions
    printf("\nModifying unions:\n");
    modifyNumber(&num1, 'f', 99.99);
    modifyNumber(&num2, 'i', 777);
    
    // Process modified unions
    printf("\nAfter modification:\n");
    processNumber(num1, 'f');
    processNumber(num2, 'i');
    
    return 0;
}

Memory Layout Analysis

Understanding Union Memory Layout

#include <stdio.h>

union MemoryTest {
    char singleByte;
    short twoBytes;
    int fourBytes;
    long long eightBytes;
    double eightByteFloat;
    char array[16];
};

void displayMemoryLayout() {
    union MemoryTest test;
    
    printf("Union Memory Layout Analysis:\n");
    printf("Union size: %zu bytes\n", sizeof(union MemoryTest));
    printf("Union address: %p\n", &test);
    
    printf("\nMember addresses:\n");
    printf("singleByte:     %p (offset: %ld)\n", &test.singleByte, 
           (char*)&test.singleByte - (char*)&test);
    printf("twoBytes:       %p (offset: %ld)\n", &test.twoBytes,
           (char*)&test.twoBytes - (char*)&test);
    printf("fourBytes:      %p (offset: %ld)\n", &test.fourBytes,
           (char*)&test.fourBytes - (char*)&test);
    printf("eightBytes:     %p (offset: %ld)\n", &test.eightBytes,
           (char*)&test.eightBytes - (char*)&test);
    printf("eightByteFloat: %p (offset: %ld)\n", &test.eightByteFloat,
           (char*)&test.eightByteFloat - (char*)&test);
    printf("array:          %p (offset: %ld)\n", &test.array,
           (char*)&test.array - (char*)&test);
    
    // Demonstrate overlapping
    test.fourBytes = 0x12345678;
    printf("\nOverlapping demonstration:\n");
    printf("Set fourBytes to 0x12345678\n");
    printf("singleByte value: 0x%02X\n", (unsigned char)test.singleByte);
    printf("twoBytes value: 0x%04X\n", (unsigned short)test.twoBytes);
    printf("array[0]: 0x%02X, array[1]: 0x%02X\n", 
           (unsigned char)test.array[0], (unsigned char)test.array[1]);
}

int main() {
    displayMemoryLayout();
    return 0;
}

Best Practices and Common Mistakes

Safe Union Usage

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

// Tagged union for safe access
typedef struct {
    enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } type;
    union {
        int intVal;
        float floatVal;
        char stringVal[50];
    } data;
} SafeUnion;

void setSafeUnion(SafeUnion *su, int type, void *value) {
    su->type = type;
    
    switch (type) {
        case TYPE_INT:
            su->data.intVal = *(int*)value;
            break;
        case TYPE_FLOAT:
            su->data.floatVal = *(float*)value;
            break;
        case TYPE_STRING:
            strncpy(su->data.stringVal, (char*)value, 49);
            su->data.stringVal[49] = '\0';
            break;
    }
}

void printSafeUnion(SafeUnion *su) {
    switch (su->type) {
        case TYPE_INT:
            printf("Integer: %d\n", su->data.intVal);
            break;
        case TYPE_FLOAT:
            printf("Float: %.2f\n", su->data.floatVal);
            break;
        case TYPE_STRING:
            printf("String: %s\n", su->data.stringVal);
            break;
        default:
            printf("Unknown type\n");
    }
}

int main() {
    SafeUnion su;
    
    int intVal = 42;
    float floatVal = 3.14f;
    char stringVal[] = "Hello Safe Union";
    
    setSafeUnion(&su, TYPE_INT, &intVal);
    printSafeUnion(&su);
    
    setSafeUnion(&su, TYPE_FLOAT, &floatVal);
    printSafeUnion(&su);
    
    setSafeUnion(&su, TYPE_STRING, stringVal);
    printSafeUnion(&su);
    
    return 0;
}

Common Mistakes to Avoid

  1. Accessing wrong member: Always track which member was last assigned
  2. Assuming data persistence: Only the last assigned member contains valid data
  3. Ignoring alignment: Union size is determined by largest member + alignment
  4. Type confusion: Use tagged unions for type safety
  5. Uninitialized access: Initialize unions before use

Summary

Accessing union elements in C follows the same syntax as structures but requires careful consideration of memory sharing. The dot operator (.) and arrow operator (->) work identically, but only the last assigned member contains valid data. Understanding union memory layout, using tagged unions for safety, and proper type tracking are essential for effective union usage in memory-constrained applications and data type conversions.


Part of BCA Programming with C Course (UGCOA22J201)