C Unions

Definition

A union is a user-defined composite type where all members share the same memory address. Unlike structures, which allocate contiguous storage for each field, unions overlay their members, meaning only one member can hold a meaningful value at any given time. The size of a union equals the size of its largest member, plus any padding required to satisfy alignment constraints. Unions are primarily used for memory-efficient variant storage, hardware register mapping, protocol parsing, and safe type-punning.

Syntax & Memory Layout

union Data {
int i;
float f;
char bytes[4];
};
PropertyBehavior
StorageAll members start at offset 0. Writing to one overwrites others.
Sizesizeof(union) == sizeof(largest_member) + alignment_padding
AlignmentEquals the strictest alignment requirement among all members
InitializationOnly the first member is implicitly zero-initialized. Designated initializers can target any member.
Active MemberProgrammer responsibility to track which field currently holds valid data

Usage Examples

1. Basic Variant Type

#include <stdio.h>
union Value {
int i;
double d;
char c;
};
int main(void) {
union Value v;
v.i = 42;
printf("Int: %d\n", v.i); // Valid
v.d = 3.14;
printf("Double: %.2f\n", v.d); // Overwrites previous value
return 0;
}

2. Anonymous Unions (C11)

typedef struct {
int type;
union {          // Anonymous: members promoted to struct scope
int count;
double ratio;
char *label;
};
} Config;
Config c = { .type = 1, .ratio = 0.75 }; // Access as c.ratio, not c.union.ratio

3. Hardware/Protocol Register Mapping

typedef union {
uint32_t raw;
struct {
uint32_t enable : 1;
uint32_t mode   : 3;
uint32_t reserved : 28;
} bits;
} ControlReg;

Rules & Constraints

  • Single Active Member: Only the most recently written member is guaranteed to hold valid data. Reading another member reinterprets the underlying bytes.
  • C23 Type-Pinning Guarantee: C23 explicitly defines reading a non-active union member as well-defined behavior. It reinterprets the object representation of the active member according to the accessed type's rules.
  • Strict Aliasing Exemption: In C, union-based type-punning is explicitly exempt from strict aliasing rules. This differs from C++, where accessing inactive union members invokes undefined behavior.
  • Padding & Endianness: Padding bytes between or after members are implementation-defined. Byte order affects multi-member interpretations (e.g., uint32_t vs char[4]).
  • Designated Initialization: union U u = { .f = 1.5f }; initializes f, zeroes the rest of the storage, and sets f as the active member.
  • No Automatic Tracking: The compiler does not track which member is active. Developers must maintain state externally (e.g., via an enum discriminant).

Best Practices

  1. Use tagged unions: Pair a union with an enum discriminant to track the active member explicitly.
   typedef enum { TYPE_INT, TYPE_FLOAT } Tag;
typedef struct { Tag tag; union { int i; float f; } data; } SafeVariant;
  1. Initialize explicitly: Use designated initializers to avoid indeterminate states and document intent.
  2. Verify size/alignment statically:
   _Static_assert(sizeof(union Data) == 4, "Unexpected union size");
  1. Document endianness assumptions: When mapping unions to network protocols or hardware, explicitly state byte-order expectations and provide conversion helpers.
  2. Prefer memcpy or explicit casting for general type-punning: If strict portability across C/C++ boundaries is required, avoid unions and use memcpy or C23 __builtin_bit_cast.
  3. Reserve unions for low-level code: Use for register mapping, variant types, or memory-constrained contexts. Prefer structs or _Generic for high-level logic.

Common Pitfalls

  • 🔮 Assuming all members are valid simultaneously: Reading an inactive member without tracking state yields garbage or reinterpreted values.
  • 🔮 Confusing union size with sum of members: sizeof(union) != ÎŁ sizeof(member). Misallocating memory causes buffer overflows or truncation.
  • 🔮 Ignoring padding in protocols: Unions may include hidden padding bytes. Direct serialization/transmission breaks across compilers/architectures.
  • 🔮 Endianness-dependent assumptions: union { uint32_t w; char b[4]; } yields different byte orders on x86 vs ARM. Fails without explicit conversion.
  • 🔮 Uninitialized active member: Accessing u.i without prior assignment reads stack/heap garbage → undefined behavior pre-C23, implementation-defined otherwise.
  • 🔮 Mixing with bitfields unpredictably: Bitfield layout within unions is implementation-defined. Order, padding, and packing vary by compiler and target.
  • 🔮 Assuming C++ compatibility: Union type-punning is well-defined in C but undefined in C++. Cross-language APIs must avoid it or use explicit serialization.

Standards & Tooling Evolution

  • C89/C90: Introduced unions with overlapping storage semantics. Type-punning behavior left implementation-defined.
  • C99: Added designated initializers for unions, improving clarity and safety.
  • C11: Introduced anonymous unions, enabling flattened variant structs and cleaner APIs.
  • C17: Maintained semantics. Clarified alignment and padding rules for complex union layouts.
  • C23: Major clarification. Reading inactive union members is now explicitly well-defined as object representation reinterpretation. Eliminates historical ambiguity around type-punning.
  • Compiler Diagnostics:
  • -Wmissing-field-initializers: Catches partially initialized unions
  • -Wstrict-aliasing: Exempt for unions in C, but warns on pointer-based punning
  • -fplan9-extensions: GCC/Clang enable anonymous union extensions
  • Static Analysis: clang-tidy, cppcheck, and Coverity flag untagged unions, unsafe inactive member access, and missing discriminant tracking.
  • Debugging Support: GDB/LLDB display all union members simultaneously. Use ptype union_name to inspect layout, and print u.member to verify active state during step-through.
  • Modern Alternatives: For high-level variant handling, consider _Generic (C11) for compile-time dispatch, or explicit serialization frameworks for network/storage protocols. Unions remain optimal for low-level, memory-constrained, or hardware-facing code.

Unions are a precise, low-overhead mechanism for overlapping memory representation in C. When paired with explicit state tracking, standardized initialization, and careful layout verification, they enable efficient variant modeling, hardware interfacing, and protocol parsing while avoiding common aliasing and padding pitfalls.

1. Mastering C Name Mangling and Symbol Decoration

Explains how compilers modify symbol names internally and how this affects linking and interoperability.
https://macronepal.com/mastering-c-name-mangling-and-symbol-decoration/

2. C No Linkage Mechanics and Scope Isolation

Covers variables and identifiers that are restricted to their local scope with no external visibility.
https://macronepal.com/c-no-linkage-mechanics-and-scope-isolation/

3. Understanding C Internal Linkage Mechanics and Architecture

Learn how internal linkage restricts symbol visibility to a single source file using static.
https://macronepal.com/understanding-c-internal-linkage-mechanics-and-architecture/

4. Mastering C External Linkage for Modular Systems

Explains how external linkage enables functions and variables to be shared across multiple files.
https://macronepal.com/mastering-c-external-linkage-for-modular-systems/

5. C Linkage

A complete overview of linkage types in C and their importance in program structure.
https://macronepal.com/c-linkage/

6. Mastering Function Prototype Scope in C

Focuses on how function prototype declarations work and where they remain visible.
https://macronepal.com/mastering-function-prototype-scope-in-c/

7. C Function Scope Mechanics and Visibility

Explains scope rules specific to function labels and declarations.
https://macronepal.com/c-function-scope-mechanics-and-visibility/

8. Understanding C File Scope Mechanics and Architecture

Learn how file-level declarations behave across translation units.
https://macronepal.com/understanding-c-file-scope-mechanics-and-architecture/

9. Mastering C Scope Rules for Predictable Name Resolution

Detailed guide to resolving identifier conflicts and understanding nested scope behavior.
https://macronepal.com/mastering-c-scope-rules-for-predictable-name-resolution/

10. C Scope Rules

A foundational overview of variable and function visibility rules in C.
https://macronepal.com/c-scope-rules/

11. Mastering C Register Storage Class for Historical Context and Modern Alternatives

Explains the legacy register keyword and why modern compilers rarely require it.
https://macronepal.com/mastering-c-register-storage-class-for-historical-context-and-modern-alternatives/

12. Mastering _Thread_local in C

Covers thread-local storage and its role in multithreaded C programming.
https://macronepal.com/mastering-_thread_local-in-c/

13. C Extern Storage Class Mechanics and Usage

Shows how extern allows access to global variables across source files.
https://macronepal.com/c-extern-storage-class-mechanics-and-usage/

14. Understanding the C Static Storage Class

Explains static lifetime, persistence, and scope control with static.
https://macronepal.com/understanding-the-c-static-storage-class-mechanics-and-usage/

15. C Auto Storage Class

Introduces automatic storage duration and stack allocation basics.
https://macronepal.com/c-auto-storage-class/

16. Advanced C Practice Resource 13757-2

Additional advanced systems programming practice content.
https://macronepal.com/13757-2/

17. Advanced C Practice Resource 13748-2

Intermediate-to-advanced C concepts for deeper learning.
https://macronepal.com/13748-2/

18. Advanced C Practice Resource 13747-2

Supplementary low-level C examples and exercises.
https://macronepal.com/13747-2/

19. Advanced C Practice Resource 13746-2

Practical implementation-focused C reference material.
https://macronepal.com/13746-2/

20. Advanced C Practice Resource 13745-2

Extra systems-level C programming study material.
https://macronepal.com/13745-2/

Best Learning Order

Scope Rules → File Scope → Function Scope → Linkage → Storage Classes → Thread Local → Name Mangling → Advanced Practice

This order builds strong understanding from visibility basics to modular system architecture in C.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper