- References
- Function Overloading
- Default Function Arguments
- Inline Function
- Dynamic Memory Allocation
- Placement New
- nullptr
- Namespaces
- Type Aliases
- Enum classes
- constexpr
- static_assert
- mutable Keyword
- auto Keyword
- Smart Pointers
- Basics of Classes
- Constructors
- Destructors
- Operator Overloading
- Copy Semantics
- Move Semantics
- Composition, RAII & Ownership
- Inheritance
- Polymorphism
- Abstraction
- Encapsulation
- Template
- Static Memory
- Friend Function
- this Pointer
- Function Pointer
- Lambdas and Callback Management
- Union
Union (Memory Sharing)

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 onlyMemory Layout (The Overlap)
Assume uint32_t i and uint8_t b[4].
Byte Offset | Union Member i (32-bit) | Union Member b (Array) |
|---|---|---|
+0 |
|
|
+1 |
|
|
+2 |
|
|
+3 |
|
|
If you write
0x11223344toi,b[0]automatically becomes0x44(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 Little Endian: Big Endian: |
❌ 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 |
|
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