Unions in C: Complete Guide with Examples
Unions in C: A Comprehensive Tutorial
A union is a user-defined data type that allows storing different data types in the same memory location. At any given time, only one member can hold a valid value because all members share the same memory. Unions are useful for saving memory when only one of several data members is needed at a time.
This tutorial provides an in-depth exploration of unions in C: declaration, size, initialization, nested unions, anonymous unions, applications, common pitfalls, and best practices.
Conceptual Introduction & Fundamentals
What is a Union?
A union is a special data type that allows storing different data types in the same memory location. All members of a union share the same memory address, so the union’s size is determined by its largest member.
Example:
c
union Data { int i; float f; char str[20]; }; This union can hold an int, a float, or a char array, but only one at a time.
Why Use Unions?
- Memory efficiency: Store different data types in the same space.
- Type punning: Interpret the same bytes as different types.
- Hardware programming: Represent registers in different ways.
- Variant data: Store either one type or another (not both simultaneously).
- Space-constrained systems: Embedded systems, protocol implementations.
Union vs. Structure
| Aspect | Union | Structure |
|---|---|---|
| Memory allocation | All members share same memory | Each member has separate memory |
| Size | Size of largest member | Sum of all members + padding |
| Value storage | One member at a time | All members simultaneously |
| Access | Last written member is valid | All members always valid |
| Use case | Mutually exclusive data | Grouped related data |
Syntax & Core Mechanics
Declaration
Syntax:
c
union UnionName { data_type member1; data_type member2; // ... }; Example:
c
union Student { int rollNo; float height; char firstLetter; }; Accessing Union Members
Use the dot operator (.) for union variables, and arrow operator (->) for union pointers.
c
#include <stdio.h> union Student { int rollNo; float height; char firstLetter; }; int main(void) { union Student data; data.rollNo = 21; printf("Roll No: %d\n", data.rollNo); data.height = 5.2f; printf("Height: %.2f\n", data.height); data.firstLetter = 'N'; printf("First Letter: %c\n", data.firstLetter); return 0; } Expected output:
text
Roll No: 21 Height: 5.20 First Letter: N
Important: Writing to one member overwrites the others.
Size of Union
The size of a union equals the size of its largest member.
c
#include <stdio.h> union A { int x; // 4 bytes char y; // 1 byte }; union B { int arr[10]; // 40 bytes char y; // 1 byte }; int main(void) { printf("sizeof(union A) = %zu\n", sizeof(union A)); printf("sizeof(union B) = %zu\n", sizeof(union B)); return 0; } Expected output (typical):
text
sizeof(union A) = 4 sizeof(union B) = 40
Memory Layout
For union A { int x; char y; };:
text
Memory (4 bytes): +---+---+---+---+ | x (int) | <- All 4 bytes used for int +---+---+---+---+ | y (char) | unused | <- 1 byte used for char, 3 unused +---+---+---+---+
All members start at the same memory address.
Initialization
Only the first member can be initialized directly (C89). C99 supports designated initializers.
c
#include <stdio.h> union Data { int i; float f; char c; }; int main(void) { /* Initialize first member */ union Data d1 = {42}; printf("d1.i = %d\n", d1.i); /* C99 designated initializer */ union Data d2 = {.f = 3.14f}; printf("d2.f = %.2f\n", d2.f); union Data d3 = {.c = 'A'}; printf("d3.c = %c\n", d3.c); return 0; } Expected output:
text
d1.i = 42 d2.f = 3.14 d3.c = A
Union with Pointers
c
#include <stdio.h> union Value { int i; double d; char *str; }; int main(void) { union Value v; union Value *ptr = &v; v.i = 100; printf("ptr->i = %d\n", ptr->i); v.d = 3.14; printf("ptr->d = %.2f\n", ptr->d); return 0; } Expected output:
text
ptr->i = 100 ptr->d = 3.14
Nested Unions
c
#include <stdio.h> union Student { int rollNo; union Academic { int marks; } performance; }; int main(void) { union Student student; student.rollNo = 21; printf("Roll No: %d\n", student.rollNo); student.performance.marks = 91; printf("Marks: %d\n", student.performance.marks); return 0; } Expected output:
text
Roll No: 21 Marks: 91
Anonymous Unions (C11)
Anonymous unions allow direct access to members without a union variable name.
c
#include <stdio.h> struct Student { int rollNo; union { // Anonymous union int marks; float grade; }; }; int main(void) { struct Student student; student.rollNo = 21; printf("Roll No: %d\n", student.rollNo); student.marks = 91; printf("Marks: %d\n", student.marks); student.grade = 8.5f; printf("Grade: %.1f\n", student.grade); return 0; } Expected output:
text
Roll No: 21 Marks: 91 Grade: 8.5
Full Practical Code Example with Explanation
This program demonstrates unions in a realistic scenario: a variant data type system for a network packet parser.
Program: Network Packet Parser
c
/** * network_packet_parser.c * Demonstrates unions for variant data types in network packets. * Shows union declaration, initialization, and type punning. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <stdint.h> #define MAX_LINE_LEN 100 /* Packet types */ typedef enum { PACKET_INT, PACKET_FLOAT, PACKET_STRING } PacketType; /* Union to hold different data types */ typedef union { int intValue; float floatValue; char stringValue[32]; } PacketData; /* Structure combining type tag and union */ typedef struct { PacketType type; PacketData data; } Packet; /* Function prototypes */ void demonstrateBasicUnion(void); void demonstrateUnionSize(void); void demonstrateUnionInitialization(void); void demonstrateTypePunning(void); void demonstratePacketSystem(void); Packet createIntPacket(int value); Packet createFloatPacket(float value); Packet createStringPacket(const char *value); void displayPacket(const Packet *packet); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Union Demonstration ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Basic Union Usage\n"); printf("2. Union Size\n"); printf("3. Union Initialization\n"); printf("4. Type Punning\n"); printf("5. Packet System (Variant Data)\n"); printf("6. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateBasicUnion(); break; case 2: demonstrateUnionSize(); break; case 3: demonstrateUnionInitialization(); break; case 4: demonstrateTypePunning(); break; case 5: demonstratePacketSystem(); break; case 6: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Demonstrates basic union usage */ void demonstrateBasicUnion(void) { printf("\n=== Basic Union Usage ===\n"); union Student { int rollNo; float height; char firstLetter; }; union Student data; data.rollNo = 21; printf("Roll No: %d\n", data.rollNo); data.height = 5.2f; printf("Height: %.2f\n", data.height); data.firstLetter = 'N'; printf("First Letter: %c\n", data.firstLetter); printf("\nNote: Only the last written member is valid.\n"); } /* Demonstrates union size */ void demonstrateUnionSize(void) { printf("\n=== Union Size ===\n"); union A { int x; // 4 bytes char y; // 1 byte }; union B { int arr[10]; // 40 bytes char y; // 1 byte }; union C { double d; // 8 bytes int i; // 4 bytes char c; // 1 byte }; printf("sizeof(union A) = %zu bytes\n", sizeof(union A)); printf("sizeof(union B) = %zu bytes\n", sizeof(union B)); printf("sizeof(union C) = %zu bytes\n", sizeof(union C)); printf("\nSize equals the size of the largest member.\n"); } /* Demonstrates union initialization */ void demonstrateUnionInitialization(void) { printf("\n=== Union Initialization ===\n"); union Data { int i; float f; char c; }; /* Initialize first member */ union Data d1 = {42}; printf("d1.i = %d\n", d1.i); /* C99 designated initializers */ union Data d2 = {.f = 3.14f}; printf("d2.f = %.2f\n", d2.f); union Data d3 = {.c = 'A'}; printf("d3.c = %c\n", d3.c); } /* Demonstrates type punning */ void demonstrateTypePunning(void) { printf("\n=== Type Punning ===\n"); union { float f; uint32_t bits; } converter; converter.f = 3.14f; printf("Float value: %.6f\n", converter.f); printf("Raw bits: 0x%08X\n", converter.bits); /* Interpret integer as float */ union { int i; float f; } pun; pun.i = 0x4048F5C3; printf("\nInteger 0x%X interpreted as float: %.2f\n", pun.i, pun.f); } /* Demonstrates packet system with unions */ void demonstratePacketSystem(void) { printf("\n=== Packet System (Variant Data) ===\n"); Packet packets[3]; packets[0] = createIntPacket(42); packets[1] = createFloatPacket(3.14f); packets[2] = createStringPacket("Hello"); for (int i = 0; i < 3; i++) { displayPacket(&packets[i]); } } /* Creates an integer packet */ Packet createIntPacket(int value) { Packet p; p.type = PACKET_INT; p.data.intValue = value; return p; } /* Creates a float packet */ Packet createFloatPacket(float value) { Packet p; p.type = PACKET_FLOAT; p.data.floatValue = value; return p; } /* Creates a string packet */ Packet createStringPacket(const char *value) { Packet p; p.type = PACKET_STRING; strncpy(p.data.stringValue, value, 31); p.data.stringValue[31] = '\0'; return p; } /* Displays a packet */ void displayPacket(const Packet *packet) { switch (packet->type) { case PACKET_INT: printf("Integer packet: %d\n", packet->data.intValue); break; case PACKET_FLOAT: printf("Float packet: %.2f\n", packet->data.floatValue); break; case PACKET_STRING: printf("String packet: %s\n", packet->data.stringValue); break; default: printf("Unknown packet type\n"); break; } } Explanation of Union Usage
- Basic union: Shows how members share memory.
- Union size: Demonstrates size equals largest member.
- Union initialization: Shows first member and designated initializers.
- Type punning: Interprets same bytes as different types.
- Packet system: Uses union with type tag for variant data.
Expected Output (sample run)
text
=== Union Demonstration === --- Menu --- 1. Basic Union Usage 2. Union Size 3. Union Initialization 4. Type Punning 5. Packet System (Variant Data) 6. Exit Enter choice: 1 === Basic Union Usage === Roll No: 21 Height: 5.20 First Letter: N Note: Only the last written member is valid. --- Menu --- ... Enter choice: 2 === Union Size === sizeof(union A) = 4 bytes sizeof(union B) = 40 bytes sizeof(union C) = 8 bytes Size equals the size of the largest member. --- Menu --- ... Enter choice: 5 === Packet System (Variant Data) === Integer packet: 42 Float packet: 3.14 String packet: Hello
Common Pitfalls, Edge Cases & Memory Considerations
1. Reading Wrong Member
Reading a union member that was not the last written is undefined behavior (except for type punning in some implementations).
c
union Data d; d.i = 42; printf("%f\n", d.f); // BUG: reading wrong member Solution: Track which member is active using a type tag (as in the packet example).
2. Assuming All Members Can Store Data Simultaneously
Unions store only one member at a time.
c
union Data d; d.i = 42; d.f = 3.14f; // Overwrites d.i printf("%d\n", d.i); // Garbage Solution: Use a structure if multiple values are needed simultaneously.
3. Union Size with Padding
Union size may include padding for alignment.
c
union Example { char c; // 1 byte int i; // 4 bytes }; /* sizeof may be 4 (with padding) */ Solution: Use sizeof to determine actual size.
4. String Assignment in Union
Cannot assign strings directly to union character arrays after declaration.
c
union Data d; d.stringValue = "Hello"; // BUG: cannot assign to array
Solution: Use strcpy(d.stringValue, "Hello");
5. Type Punning Portability
Type punning through unions is implementation-defined in C (though widely supported). The C standard does not guarantee reading a different member than the last written.
Solution: Use memcpy for portable type punning, or use unions only where supported.
6. Anonymous Union Compatibility
Anonymous unions are a C11 feature; they may not be supported in C99 compilers.
Solution: Ensure compiler supports C11 (-std=c11).
7. Uninitialized Union
Reading an uninitialized union is undefined behavior.
c
union Data d; printf("%d\n", d.i); // Garbage Solution: Initialize: union Data d = {0};
8. Nested Union Access
Accessing deeply nested unions requires multiple dot operators.
c
union Outer { union Inner { int x; } inner; }; /* Access: outer.inner.x */ 9. Memory Overlap
All union members start at the same address. Modifying one member affects the others.
c
union Data d; d.i = 0x41424344; printf("%c\n", d.c); // Prints 'D' (on little-endian systems) Solution: Be aware of endianness when type punning.
10. Memory Considerations
- Union size = size of largest member (+ padding).
- Saves memory compared to structures when only one member is needed.
- Type punning can be efficient but non-portable.
- Unions are often used in embedded systems to save limited RAM.
Summary & Best Practices
Unions provide a memory-efficient way to store different data types in the same location. They are ideal for variant data, type punning, and hardware programming.
Key Takeaways
- Union members share the same memory.
- Union size = size of largest member.
- Only one member is valid at a time.
- Use dot operator (
.) for access, arrow (->) for pointers. - C99 supports designated initializers.
- Anonymous unions (C11) allow direct member access.
- Track active member with a type tag.
Best Practices
- Use a type tag (enum) to track which union member is active.
- Initialize unions before use.
- Use designated initializers (C99) for clarity.
- Avoid reading the wrong member unless type punning is intended.
- Document the active member in comments.
- Use unions for memory-constrained environments.
- Use
memcpyfor portable type punning instead of union type punning. - Be aware of endianness when interpreting bytes.
- Use
sizeofto determine union size. - Prefer structures when all members are needed simultaneously.
By mastering unions, you’ll write memory-efficient C programs for embedded systems, network protocols, and variant data handling.




