CoreTechED

Loading

Learn C unions: declaration, size, initialization, nested unions, anonymous unions, and type punning. Master memory-efficient data storage

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

AspectUnionStructure
Memory allocationAll members share same memoryEach member has separate memory
SizeSize of largest memberSum of all members + padding
Value storageOne member at a timeAll members simultaneously
AccessLast written member is validAll members always valid
Use caseMutually exclusive dataGrouped 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

  1. Basic union: Shows how members share memory.
  2. Union size: Demonstrates size equals largest member.
  3. Union initialization: Shows first member and designated initializers.
  4. Type punning: Interprets same bytes as different types.
  5. 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

  1. Use a type tag (enum) to track which union member is active.
  2. Initialize unions before use.
  3. Use designated initializers (C99) for clarity.
  4. Avoid reading the wrong member unless type punning is intended.
  5. Document the active member in comments.
  6. Use unions for memory-constrained environments.
  7. Use memcpy for portable type punning instead of union type punning.
  8. Be aware of endianness when interpreting bytes.
  9. Use sizeof to determine union size.
  10. 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.