C Unions
Sharing memory space efficiently
🔄 What are C Unions?
Unions in C allow multiple variables to share the same memory location, saving space by storing only one value at a time from different data types.
// Union shares memory among members
union Data {
int i;
float f;
char c;
};
Union Features
Memory Sharing
All members share same memory
union Data d;
d.i = 10; // Uses shared memory
Size
Size equals largest member
// Size = sizeof(largest member)
sizeof(union Data)
One Value
Only one member active at a time
d.i = 5; // i is active
d.f = 3.14; // Now f is active
Efficiency
Saves memory space
// Efficient for variant data
union Number num;
🔹 Basic Union Example
Unions in C allow multiple members to share the same memory location, enabling you to interpret the same data in different ways while using minimal memory. Unlike structures where each member gets its own memory, a union's size equals its largest member, and only one member can hold a value at a time. You define a union like union Data {int i; float f; char c;}; and access members with the dot operator. When you assign a value to one member, previous values in other members become undefined. Unions are useful for memory-constrained environments, implementing variant types, or when working with different interpretations of the same binary data, such as in network protocols or file formats.
#include <stdio.h>
union Data {
int i;
float f;
char c;
};
int main() {
union Data data;
printf("Size of union: %lu bytes\n", sizeof(data));
// Assign integer value
data.i = 42;
printf("data.i = %d\n", data.i);
printf("data.f = %f\n", data.f); // Garbage value
printf("data.c = %c\n", data.c); // Garbage value
// Assign float value
data.f = 3.14;
printf("\nAfter assigning float:\n");
printf("data.i = %d\n", data.i); // Garbage value
printf("data.f = %f\n", data.f);
printf("data.c = %c\n", data.c); // Garbage value
// Assign char value
data.c = 'A';
printf("\nAfter assigning char:\n");
printf("data.i = %d\n", data.i); // Garbage value
printf("data.f = %f\n", data.f); // Garbage value
printf("data.c = %c\n", data.c);
return 0;
}
Output:
Size of union: 4 bytes data.i = 42 data.f = 0.000000 data.c = * After assigning float: data.i = 1078523331 data.f = 3.140000 data.c = C After assigning char: data.i = 65 data.f = 0.000000 data.c = A
🔹 Union vs Structure Comparison
Understanding the memory difference between unions and structures is crucial for choosing the right tool and optimizing memory usage in your programs. A structure allocates separate memory for each member, so struct {int i; float f; char c;} uses approximately 9 bytes (accounting for padding). A union allocates only enough memory for the largest member, so union {int i; float f; char c;} uses only about 4 bytes. Structures are appropriate when you need all members to hold values simultaneously, while unions are suitable when only one member is relevant at a time. This fundamental difference makes unions valuable for memory optimization but requires careful programming to avoid accessing inactive members, which leads to undefined behavior.
#include <stdio.h>
// Structure - each member has separate memory
struct StructData {
int i;
float f;
char c;
};
// Union - all members share same memory
union UnionData {
int i;
float f;
char c;
};
int main() {
struct StructData s;
union UnionData u;
printf("Structure size: %lu bytes\n", sizeof(s));
printf("Union size: %lu bytes\n", sizeof(u));
// Structure can hold all values simultaneously
s.i = 10;
s.f = 3.14;
s.c = 'X';
printf("\nStructure values:\n");
printf("s.i = %d, s.f = %.2f, s.c = %c\n", s.i, s.f, s.c);
// Union can hold only one value at a time
u.i = 10;
printf("\nUnion after setting integer:\n");
printf("u.i = %d\n", u.i);
u.f = 3.14;
printf("\nUnion after setting float:\n");
printf("u.f = %.2f\n", u.f);
printf("u.i = %d (corrupted)\n", u.i);
return 0;
}
Output:
Structure size: 12 bytes Union size: 4 bytes Structure values: s.i = 10, s.f = 3.14, s.c = X Union after setting integer: u.i = 10 Union after setting float: u.f = 3.14 u.i = 1078523331 (corrupted)
🔹 Practical Union Example
Unions excel at implementing variant data types that can hold different types of values at different times, providing flexibility with minimal memory overhead. A common pattern combines a union with an enum to track which member is currently active, like creating a union Value that can hold int, float, or string, paired with an enum Type indicating the current type. This approach is foundational for building interpreters, configuration systems, or any application that handles values of multiple types. The union provides space efficiency by sharing memory, while the enum ensures type safety by tracking which interpretation is valid. This pattern bridges the gap between C's static typing and the need for flexible data representation.
#include <stdio.h>
enum DataType {
INTEGER,
FLOAT,
CHARACTER
};
struct Variant {
enum DataType type;
union {
int i;
float f;
char c;
} value;
};
void printVariant(struct Variant v) {
switch(v.type) {
case INTEGER:
printf("Integer: %d\n", v.value.i);
break;
case FLOAT:
printf("Float: %.2f\n", v.value.f);
break;
case CHARACTER:
printf("Character: %c\n", v.value.c);
break;
}
}
int main() {
struct Variant data[3];
// Store different types of data
data[0].type = INTEGER;
data[0].value.i = 42;
data[1].type = FLOAT;
data[1].value.f = 3.14159;
data[2].type = CHARACTER;
data[2].value.c = 'Z';
// Print all variants
printf("Variant data:\n");
for(int i = 0; i < 3; i++) {
printf("%d. ", i+1);
printVariant(data[i]);
}
printf("\nSize of each variant: %lu bytes\n", sizeof(struct Variant));
return 0;
}
Output:
Variant data: 1. Integer: 42 2. Float: 3.14 3. Character: Z Size of each variant: 8 bytes
🔹 Union with Bit Fields
Combining unions with bit fields creates powerful tools for low-level programming, enabling you to view the same data at both bit-level and larger-type granularity. A common pattern uses a union with one member as a full integer and another as a structure with bit fields, like union {unsigned int value; struct {unsigned int bit0:1; unsigned int bit1:1; ... } bits;} This allows you to manipulate hardware registers or packed data by either setting individual bits through the bit field structure or accessing the complete value as an integer. This technique is indispensable in embedded systems programming, device drivers, and network protocol implementations where you need both fine-grained bit control and efficient word-level access to hardware registers.
#include <stdio.h>
union Register {
unsigned int full;
struct {
unsigned int bit0 : 1;
unsigned int bit1 : 1;
unsigned int bit2 : 1;
unsigned int bit3 : 1;
unsigned int remaining : 28;
} bits;
};
int main() {
union Register reg;
// Set the full register value
reg.full = 0b1010; // Binary: 1010 (decimal: 10)
printf("Full register value: %u\n", reg.full);
printf("Individual bits:\n");
printf("Bit 0: %u\n", reg.bits.bit0);
printf("Bit 1: %u\n", reg.bits.bit1);
printf("Bit 2: %u\n", reg.bits.bit2);
printf("Bit 3: %u\n", reg.bits.bit3);
// Modify individual bits
reg.bits.bit1 = 1;
reg.bits.bit3 = 0;
printf("\nAfter modification:\n");
printf("Full register value: %u\n", reg.full);
printf("Binary representation: ");
for(int i = 3; i >= 0; i--) {
printf("%d", (reg.full >> i) & 1);
}
printf("\n");
return 0;
}
Output:
Full register value: 10 Individual bits: Bit 0: 0 Bit 1: 1 Bit 2: 0 Bit 3: 1 After modification: Full register value: 6 Binary representation: 0110