Union (Memory Sharing)

cardimg

A Struct is like a House where every member gets their own room.

A Union is like a Fitting Room. Only one person (member) can use it at a time.

In a Union, all members share the same memory address. Writing to one member overwrites the others.

  • Size: The size of a Union is equal to the size of its largest member.

  • Purpose: To store different types of data in the same spot, or to view the same data in different formats (Type Punning).

Syntax & Usage

Basic Declaration

union PacketData {
    uint32_t intValue;
    float floatValue;
    uint8_t bytes[4];
};

int main() {
    PacketData data;
    
    // 1. Store a float
    data.floatValue = 3.14f;
    
    // 2. Read it as raw bytes (Common in Drivers)
    // data.bytes[0] ... data.bytes[3] now hold the IEEE 754 representation of 3.14
    // NOTE: In strict C++, reading the "wrong" member is Undefined Behavior,
    // but in Embedded Compilers (GCC/ARM), this is supported and standard practice.
}

Anonymous Unions (The Embedded Standard)

Often nested inside structs to provide "Raw vs Bit" access without needing a variable name for the union itself.

struct Register {
    union {
        uint32_t whole_reg; // Access full 32 bits
        struct {
            uint32_t enable : 1;
            uint32_t mode   : 2;
            uint32_t error  : 1;
            uint32_t rsvd   : 28;
        } bits; // Access specific fields
    };
};

Register CTRL;
CTRL.whole_reg = 0x00; // Clear all
CTRL.bits.enable = 1;  // Set bit 0 only

Memory Layout (The Overlap)

Assume uint32_t i and uint8_t b[4].

Byte Offset

Union Member i (32-bit)

Union Member b (Array)

+0

i (Byte 0)

b[0]

+1

i (Byte 1)

b[1]

+2

i (Byte 2)

b[2]

+3

i (Byte 3)

b[3]

  • If you write 0x11223344 to i, b[0] automatically becomes 0x44 (on Little Endian systems).

Relevance in Embedded/Firmware

A. Hardware Register Mapping

This is the most common use case. Vendor header files (like STM32 HAL) use unions to allow you to write to the whole register (regs->CR1 = 0;) or specific bits (regs->CR1_b.EN = 1;) efficiently.

B. Data Serialization (Endianness Handling)

Sending a float over UART or I2C? You can't send a "float". You must send bytes.

A union allows you to simply dump the byte array u.bytes into the UART buffer without messy pointer casting or bit-shifting logic.

union FloatConverter {
    float f;
    uint8_t b[4];
};

void send_sensor_data(float val) {
    FloatConverter u;
    u.f = val;
    UART_Transmit(u.b, 4); // Sends the raw bytes of the float
}

C. Variant Packets (Polymorphic Data)

If a protocol sends different packet types based on a "Message ID", you use a union to hold the payload.

struct Message {
    uint8_t type;
    union {
        struct { int x, y; } gps;
        struct { float volt; } battery;
        struct { char text[10]; } log;
    } payload;
};
// Size of 'payload' is max(sizeof(gps), sizeof(battery), sizeof(log)).
// It does not waste RAM storing all three.

Modern C++ Alternative (std::variant)

In high-level C++ (Linux/Desktop), raw unions are considered unsafe because you don't know which type is currently active.

C++17 introduced std::variant, which is a "Type-Safe Union". It remembers what it holds.

Embedded Note: std::variant is often too heavy for small microcontrollers (uses more RAM/Rom). Raw Unions remain the standard for drivers.

Common Pitfalls (Practical Tips)

Pitfall

Details

❌ Endianness

Using unions to split uint16_t into two uint8_ts depends on the CPU.

Little Endian: val=0x1234 -> b[0]=0x34.

Big Endian: val=0x1234 -> b[0]=0x12. Code is not portable.

❌ Constructors

If a Union member has a complex Constructor (like String or Vector), the Union cannot initialize it automatically. C++11 relaxed this, but you must manually call Placement New/Destructors. Avoid complex objects in Unions.

❌ Type Punning (UB)

The C++ Standard says: "If you write to float, you can ONLY read from float." Reading bytes is technically Undefined Behavior. However, gcc/clang explicitly support this for unions. It works, but know that it's technically non-standard.

✅ Zero Initialization

union U u = {0}; only initializes the first member declared in the union. Be careful if the second member is larger than the first.

Summary Checklist

  • Use Unions to save RAM when storing mutually exclusive data.

  • Use Anonymous Unions inside Structs for Register Bitfields.

  • Be aware of Endianness when using Unions for serialization.

  • Stick to "POD" (Plain Old Data) types inside unions (int, float, pointers). Avoid Classes.

 

 

 

Concept understood? Let's apply and learn for real

Practice now