Docs

Unions

Unions in C

Table of Contents

  1. •Introduction
  2. •Union Declaration and Definition
  3. •Memory Layout
  4. •Accessing Union Members
  5. •Unions vs Structures
  6. •Practical Use Cases
  7. •Tagged Unions
  8. •Unions with Structures
  9. •Anonymous Unions
  10. •Pointer to Union
  11. •Best Practices
  12. •Common Pitfalls
  13. •Summary

Introduction

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

Key Characteristics

  • •All members share the same memory location
  • •Size of union = size of largest member
  • •Only one member can hold a value at a time
  • •Useful for memory optimization and type punning

When to Use Unions

  1. •Memory conservation: When you need to store only one of several types
  2. •Type conversion: For viewing data as different types
  3. •Variant data: When a variable can be one of several types
  4. •Hardware access: For accessing bit patterns in different ways

Union Declaration and Definition

Basic Syntax

union UnionName {
    type1 member1;
    type2 member2;
    // ... more members
};

Examples

// Simple union
union Number {
    int integer;
    float decimal;
    char bytes[4];
};

// Union with different types
union Data {
    int i;
    double d;
    char str[20];
};

// Using typedef
typedef union {
    unsigned char bytes[4];
    unsigned int word;
} WordBytes;

Declaration and Initialization

// Declare union variable
union Number num;

// Initialize with first member
union Number num1 = {42};            // Initializes 'integer'

// Designated initializer (C99+)
union Number num2 = {.decimal = 3.14}; // Initializes 'decimal'

// Direct assignment
union Number num3;
num3.integer = 100;

Memory Layout

How Unions Store Data

All members start at the same memory address:

union Number {
    int integer;      // 4 bytes
    float decimal;    // 4 bytes
    char bytes[4];    // 4 bytes
};

Memory Layout (all at same location):
Address: 0x1000
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│         4 bytes                 │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ ← integer starts here           │
│ ← decimal starts here           │
│ ← bytes[0] starts here          │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

sizeof(union Number) = 4 bytes (size of largest member)

Larger Union Example

union Data {
    char c;         // 1 byte
    int i;          // 4 bytes
    double d;       // 8 bytes
    char str[20];   // 20 bytes
};

sizeof(union Data) = 20 bytes (size of str[])

Memory:
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                  20 bytes                  │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│c│     i     │        d       │  str[20]    │
│ ↑           ↑                ↑             │
│ All start at address 0                     │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Size Comparison

struct S {
    char c;     // 1 byte + padding
    int i;      // 4 bytes
    double d;   // 8 bytes
};

union U {
    char c;     // 1 byte
    int i;      // 4 bytes
    double d;   // 8 bytes
};

sizeof(struct S) = 16 bytes (or more with padding)
sizeof(union U) = 8 bytes (size of double)

Accessing Union Members

Using Dot Operator

union Number num;

// Write to different members
num.integer = 42;
printf("As integer: %d\n", num.integer);

num.decimal = 3.14;
printf("As float: %f\n", num.decimal);

num.bytes[0] = 'A';
printf("First byte: %c\n", num.bytes[0]);

Important: Only One Member Valid at a Time

union Number num;

num.integer = 42;
printf("integer: %d\n", num.integer);  // OK: 42

num.decimal = 3.14;
printf("decimal: %f\n", num.decimal);  // OK: 3.14

// Warning: integer value is now corrupted!
printf("integer: %d\n", num.integer);  // Garbage/undefined!

Using Arrow Operator with Pointers

union Number num;
union Number *ptr = #

ptr->integer = 100;
printf("Value: %d\n", ptr->integer);

Unions vs Structures

Comparison Table

FeatureStructureUnion
MemoryEach member has own spaceAll members share space
SizeSum of all members (+padding)Size of largest member
AccessAll members valid simultaneouslyOnly one member valid at a time
Use CaseGroup related dataStore one of several types

Visual Comparison

struct Example {              union Example {
    int a;                        int a;
    float b;                      float b;
    char c;                       char c;
};                            };

Memory:                       Memory:
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ← a               ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ← a, b, c
│  int a  │                   │ shared  │   (all overlap)
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ ← b               │  space  │
│ float b │                   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ ← c               Size: 4 bytes
│ char c  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
Size: 12 bytes (with padding)

Code Example

#include <stdio.h>

struct DataStruct {
    int i;
    float f;
    char c;
};

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

int main() {
    printf("struct size: %zu bytes\n", sizeof(struct DataStruct));
    printf("union size: %zu bytes\n", sizeof(union DataUnion));

    // Structure: all members work together
    struct DataStruct ds;
    ds.i = 42;
    ds.f = 3.14;
    ds.c = 'A';
    printf("Struct: i=%d, f=%.2f, c=%c\n", ds.i, ds.f, ds.c);

    // Union: only last assigned member is valid
    union DataUnion du;
    du.i = 42;
    du.f = 3.14;  // This overwrites i
    printf("Union after f=3.14: i=%d (garbage), f=%.2f\n", du.i, du.f);

    return 0;
}

Practical Use Cases

Use Case 1: Type Punning (Viewing Data as Different Types)

// View float as integer bits
union FloatInt {
    float f;
    unsigned int bits;
};

union FloatInt fi;
fi.f = 3.14f;
printf("Float 3.14 as hex: 0x%08X\n", fi.bits);

// Examine float byte by byte
union FloatBytes {
    float f;
    unsigned char bytes[4];
};

union FloatBytes fb;
fb.f = 1.0f;
printf("Bytes of 1.0f: ");
for (int i = 0; i < 4; i++) {
    printf("%02X ", fb.bytes[i]);
}
// Output: 00 00 80 3F (little-endian IEEE 754)

Use Case 2: IP Address Representation

union IPAddress {
    unsigned int address;       // 32-bit address
    unsigned char octets[4];    // 4 octets
};

union IPAddress ip;
ip.address = 0xC0A80001;  // 192.168.0.1 in hex

printf("IP: %d.%d.%d.%d\n",
       ip.octets[3], ip.octets[2],
       ip.octets[1], ip.octets[0]);
// Output: 192.168.0.1

Use Case 3: Hardware Register Access

union StatusRegister {
    unsigned char value;
    struct {
        unsigned char ready : 1;
        unsigned char error : 1;
        unsigned char overflow : 1;
        unsigned char : 5;  // unused bits
    } bits;
};

union StatusRegister status;
status.value = 0x03;  // Set from hardware

if (status.bits.ready) printf("Device ready\n");
if (status.bits.error) printf("Error occurred\n");

Use Case 4: Network Protocol Headers

union NetworkPacket {
    char raw[64];
    struct {
        unsigned char version;
        unsigned char type;
        unsigned short length;
        unsigned int source;
        unsigned int dest;
        char payload[52];
    } header;
};

// Read raw bytes, access as structured header
union NetworkPacket packet;
// ... receive raw bytes into packet.raw ...
printf("Packet type: %d, length: %d\n",
       packet.header.type, packet.header.length);

Tagged Unions

Introduction

A tagged union (or discriminated union) combines a union with a tag indicating which member is currently valid.

Implementation

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

// Tag enumeration
enum DataType {
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING
};

// Tagged union structure
struct Variant {
    enum DataType type;  // Tag
    union {
        int i;
        float f;
        char str[20];
    } data;              // Union
};

// Constructor functions
struct Variant makeInt(int value) {
    struct Variant v;
    v.type = TYPE_INT;
    v.data.i = value;
    return v;
}

struct Variant makeFloat(float value) {
    struct Variant v;
    v.type = TYPE_FLOAT;
    v.data.f = value;
    return v;
}

struct Variant makeString(const char *value) {
    struct Variant v;
    v.type = TYPE_STRING;
    strncpy(v.data.str, value, sizeof(v.data.str) - 1);
    v.data.str[sizeof(v.data.str) - 1] = '\0';
    return v;
}

// Print function
void printVariant(struct Variant v) {
    switch (v.type) {
        case TYPE_INT:
            printf("Integer: %d\n", v.data.i);
            break;
        case TYPE_FLOAT:
            printf("Float: %.2f\n", v.data.f);
            break;
        case TYPE_STRING:
            printf("String: %s\n", v.data.str);
            break;
    }
}

int main() {
    struct Variant values[3];
    values[0] = makeInt(42);
    values[1] = makeFloat(3.14);
    values[2] = makeString("Hello");

    for (int i = 0; i < 3; i++) {
        printVariant(values[i]);
    }

    return 0;
}

Use Case: JSON-like Values

enum JSONType {
    JSON_NULL,
    JSON_BOOL,
    JSON_NUMBER,
    JSON_STRING,
    JSON_ARRAY,
    JSON_OBJECT
};

struct JSONValue {
    enum JSONType type;
    union {
        int boolean;
        double number;
        char *string;
        struct JSONValue *array;
        // ... more types
    };
};

Unions with Structures

Structure Inside Union

union Message {
    struct {
        int code;
        char text[100];
    } error;

    struct {
        int id;
        float value;
    } data;

    struct {
        char type;
        int count;
    } control;
};

Union Inside Structure (Most Common)

struct Packet {
    int type;  // Determines which union member to use
    int length;

    union {
        struct { int x, y; } position;
        struct { float r, g, b; } color;
        char text[256];
    } payload;
};

// Usage
struct Packet p;
p.type = 1;  // Position type
p.payload.position.x = 100;
p.payload.position.y = 200;

p.type = 2;  // Color type
p.payload.color.r = 1.0f;
p.payload.color.g = 0.5f;
p.payload.color.b = 0.0f;

Anonymous Unions

C11 Feature

Anonymous unions allow accessing members directly without union name:

struct Widget {
    int type;
    union {  // Anonymous union
        int intValue;
        float floatValue;
        char *stringValue;
    };  // No name!
};

// Direct access without union name
struct Widget w;
w.type = 0;
w.intValue = 42;  // Not: w.data.intValue

w.type = 1;
w.floatValue = 3.14;  // Direct access

Before C11 Comparison

// C99 style (with union name)
struct WidgetOld {
    int type;
    union {
        int intValue;
        float floatValue;
    } data;  // Named
};

struct WidgetOld w;
w.data.intValue = 42;  // Need 'data.'

// C11 style (anonymous)
struct WidgetNew {
    int type;
    union {
        int intValue;
        float floatValue;
    };  // Anonymous
};

struct WidgetNew w;
w.intValue = 42;  // Direct access

Pointer to Union

Basic Usage

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

union Data data;
union Data *ptr = &data;

// Access with arrow operator
ptr->i = 42;
ptr->f = 3.14;

Dynamic Allocation

union Data *ptr = malloc(sizeof(union Data));

if (ptr != NULL) {
    ptr->f = 2.718;
    printf("Value: %.3f\n", ptr->f);
    free(ptr);
}

Array of Unions

union Data *array = malloc(10 * sizeof(union Data));

if (array != NULL) {
    for (int i = 0; i < 10; i++) {
        array[i].i = i * 10;
    }
    free(array);
}

Best Practices

1. Always Track Active Member

// GOOD: Use a tag
struct SafeUnion {
    enum { INT, FLOAT, STRING } type;
    union {
        int i;
        float f;
        char str[20];
    } value;
};

// BAD: No way to know which member is valid
union UnsafeUnion {
    int i;
    float f;
};

2. Initialize Before Use

// GOOD
union Data d = {0};  // Initialize
d.i = 42;

// BAD
union Data d;
printf("%d\n", d.i);  // Undefined!

3. Use Designated Initializers

// C99 designated initializer
union Number n = {.f = 3.14};  // Clear which member

// Less clear
union Number n = {3};  // Which member?

4. Document the Protocol

/*
 * NetworkMessage union usage:
 * - type == 1: use request member
 * - type == 2: use response member
 * - type == 3: use error member
 */
union NetworkMessage {
    struct RequestData request;
    struct ResponseData response;
    struct ErrorData error;
};

5. Be Careful with Alignment

// May have padding issues
union Aligned {
    char c;
    double d;  // 8-byte alignment
};

// sizeof is 8, not 1

Common Pitfalls

Pitfall 1: Reading Wrong Member

union Data {
    int i;
    float f;
};

union Data d;
d.i = 42;
printf("%f\n", d.f);  // Garbage! Not the integer 42.0

Pitfall 2: Assuming Type Conversion

union Convert {
    int i;
    float f;
};

union Convert c;
c.i = 100;
// c.f is NOT 100.0f!
// It's the float interpretation of bit pattern 0x64

Pitfall 3: String Buffer Overflow

union Data {
    char str[10];
    int i;
};

union Data d;
strcpy(d.str, "This is too long!");  // Buffer overflow!

Pitfall 4: Endianness Issues

union {
    unsigned int word;
    unsigned char bytes[4];
} u;

u.word = 0x12345678;

// On little-endian: bytes = {0x78, 0x56, 0x34, 0x12}
// On big-endian: bytes = {0x12, 0x34, 0x56, 0x78}

Summary

Key Concepts

ConceptDescription
UnionData type where all members share same memory
SizeEqual to size of largest member
Active MemberOnly one member holds valid data at a time
Tagged UnionUnion + type indicator for safety
Type PunningViewing same bits as different types

Syntax Reference

// Declaration
union Name {
    type1 member1;
    type2 member2;
};

// Variable
union Name var;

// Initialization
union Name var = {value};           // First member
union Name var = {.member = value}; // Designated (C99)

// Access
var.member       // Direct
ptr->member      // Through pointer

// Size
sizeof(union Name)  // Size of largest member

Quick Comparison

Structure (struct):          Union (union):
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ member1 │                  │ All     │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤                  │ members │
│ member2 │                  │ share   │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤                  │ this    │
│ member3 │                  │ space   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
Size: sum of members         Size: largest member
All members valid            One member valid

Navigation

Unions - C Programming Tutorial | DeepML