CodeNebula
C++98corebeginner

Unions

C++98 · The Foundation · Compiler flag: -std=c++98

Explanation

A type where all members share the same memory location, holding only one member's value at a time.

Real-World Scenario

Store either an integer or a float in a memory-constrained embedded sensor reading.

Quick Snippet

snippet.cpp
union Data { int i; float f; }; Data d; d.i = 42;

Full Example

unions.cpp
// CodeNebula - C++98 Example: Unions
// Compile: g++ -std=c++98 -Wall -Wextra -o example unions.cpp

// Demonstrates unions in C++98: shared memory between members, size behavior,
// type-punning, tagged unions (discriminated unions), and practical uses.

#include <iostream>
#include <cstring>

// ------------------------------------------------------------------
// 1. Basic union - all members share the same memory
// ------------------------------------------------------------------
union BasicValue {
    int    i;
    float  f;
    char   c;
};

// ------------------------------------------------------------------
// 2. Tagged (discriminated) union - safe pattern using an enum tag
// ------------------------------------------------------------------
enum ValueType {
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING
};

struct TaggedValue {
    ValueType type;
    union {
        int   intVal;
        float floatVal;
        char  strVal[32];
    } data;
};

void printTaggedValue(const TaggedValue& v)
{
    switch (v.type) {
        case TYPE_INT:
            std::cout << "  [int]    " << v.data.intVal << std::endl;
            break;
        case TYPE_FLOAT:
            std::cout << "  [float]  " << v.data.floatVal << std::endl;
            break;
        case TYPE_STRING:
            std::cout << "  [string] " << v.data.strVal << std::endl;
            break;
    }
}

// ------------------------------------------------------------------
// 3. Union for examining byte representation (type-punning)
// ------------------------------------------------------------------
union FloatBytes {
    float value;
    unsigned char bytes[sizeof(float)];
};

int main()
{
    // ------------------------------------------------------------------
    // Demo 1: Shared memory in a basic union
    // ------------------------------------------------------------------
    std::cout << "=== Basic Union ===" << std::endl;

    BasicValue val;

    // Write as int
    val.i = 42;
    std::cout << "  After setting i = 42:" << std::endl;
    std::cout << "    val.i = " << val.i << std::endl;
    std::cout << "    val.f = " << val.f << " (garbage - reading wrong member)" << std::endl;
    std::cout << "    val.c = " << static_cast<int>(val.c) << " (low byte of int)" << std::endl;

    // Write as float - overwrites the same memory
    val.f = 3.14f;
    std::cout << "  After setting f = 3.14:" << std::endl;
    std::cout << "    val.f = " << val.f << std::endl;
    std::cout << "    val.i = " << val.i << " (reinterpreted bits)" << std::endl;

    // Write as char - overwrites only the first byte
    val.c = 'X';
    std::cout << "  After setting c = 'X':" << std::endl;
    std::cout << "    val.c = " << val.c << " (ASCII " << static_cast<int>(val.c) << ")" << std::endl;
    std::cout << std::endl;

    // ------------------------------------------------------------------
    // Demo 2: Union size = size of largest member
    // ------------------------------------------------------------------
    std::cout << "=== Union Size ===" << std::endl;
    std::cout << "  sizeof(int)        : " << sizeof(int) << std::endl;
    std::cout << "  sizeof(float)      : " << sizeof(float) << std::endl;
    std::cout << "  sizeof(char)       : " << sizeof(char) << std::endl;
    std::cout << "  sizeof(BasicValue) : " << sizeof(BasicValue)
              << " (equals largest member)" << std::endl;
    std::cout << std::endl;

    // ------------------------------------------------------------------
    // Demo 3: All members share the same address
    // ------------------------------------------------------------------
    std::cout << "=== Shared Address ===" << std::endl;
    std::cout << "  Address of val   : " << static_cast<void*>(&val) << std::endl;
    std::cout << "  Address of val.i : " << static_cast<void*>(&val.i) << std::endl;
    std::cout << "  Address of val.f : " << static_cast<void*>(&val.f) << std::endl;
    std::cout << "  Address of val.c : " << static_cast<void*>(&val.c) << std::endl;
    std::cout << "  (All addresses are the same)" << std::endl;
    std::cout << std::endl;

    // ------------------------------------------------------------------
    // Demo 4: Tagged union (discriminated union pattern)
    // ------------------------------------------------------------------
    std::cout << "=== Tagged Union ===" << std::endl;

    TaggedValue values[3];

    values[0].type = TYPE_INT;
    values[0].data.intVal = 100;

    values[1].type = TYPE_FLOAT;
    values[1].data.floatVal = 2.718f;

    values[2].type = TYPE_STRING;
    std::strcpy(values[2].data.strVal, "Hello, C++98!");

    for (int i = 0; i < 3; ++i) {
        printTaggedValue(values[i]);
    }
    std::cout << std::endl;

    // ------------------------------------------------------------------
    // Demo 5: Examining float byte representation
    // ------------------------------------------------------------------
    std::cout << "=== Float Byte Representation ===" << std::endl;

    FloatBytes fb;
    fb.value = 1.0f;

    std::cout << "  Float value: " << fb.value << std::endl;
    std::cout << "  Bytes (hex):";
    for (std::size_t i = 0; i < sizeof(float); ++i) {
        // Print each byte in hex
        int byte = static_cast<int>(fb.bytes[i]);
        std::cout << " ";
        if (byte < 16) std::cout << "0";
        // Manual hex output for C++98 compatibility
        const char hex[] = "0123456789ABCDEF";
        std::cout << hex[byte / 16] << hex[byte % 16];
    }
    std::cout << std::endl;

    fb.value = -1.0f;
    std::cout << "  Float value: " << fb.value << std::endl;
    std::cout << "  Bytes (hex):";
    for (std::size_t i = 0; i < sizeof(float); ++i) {
        int byte = static_cast<int>(fb.bytes[i]);
        std::cout << " ";
        const char hex[] = "0123456789ABCDEF";
        std::cout << hex[byte / 16] << hex[byte % 16];
    }
    std::cout << std::endl;
    std::cout << std::endl;

    // ------------------------------------------------------------------
    // Demo 6: Anonymous union inside a struct
    // ------------------------------------------------------------------
    std::cout << "=== Anonymous Union in Struct ===" << std::endl;
    std::cout << "  sizeof(TaggedValue)       : " << sizeof(TaggedValue) << std::endl;
    std::cout << "  sizeof(TaggedValue::data) : "
              << sizeof(values[0].data) << std::endl;
    std::cout << "  (Struct adds the enum tag overhead + padding)" << std::endl;

    return 0;
}

/*
Expected output (values may vary by platform/endianness):

=== Basic Union ===
  After setting i = 42:
    val.i = 42
    val.f = 5.88545e-44 (garbage - reading wrong member)
    val.c = 42 (low byte of int)
  After setting f = 3.14:
    val.f = 3.14
    val.i = 1078523331 (reinterpreted bits)
  After setting c = 'X':
    val.c = X (ASCII 88)

=== Union Size ===
  sizeof(int)        : 4
  sizeof(float)      : 4
  sizeof(char)       : 1
  sizeof(BasicValue) : 4 (equals largest member)

=== Shared Address ===
  Address of val   : 0x7ffd...
  Address of val.i : 0x7ffd...
  Address of val.f : 0x7ffd...
  Address of val.c : 0x7ffd...
  (All addresses are the same)

=== Tagged Union ===
  [int]    100
  [float]  2.718
  [string] Hello, C++98!

=== Float Byte Representation ===
  Float value: 1
  Bytes (hex): 00 00 80 3F   (little-endian IEEE 754)
  Float value: -1
  Bytes (hex): 00 00 80 BF

=== Anonymous Union in Struct ===
  sizeof(TaggedValue)       : 36
  sizeof(TaggedValue::data) : 32
  (Struct adds the enum tag overhead + padding)
*/
Run command
g++ -std=c++98 -Wall -Wextra -o example unions.cpp