C Unions
A union is a special data type in C that allows you to store different data types in the same memory location. Unlike structures where each member has its own storage location, all members of a union share the same memory.
What is a Union?
A union in C is defined using the union
keyword, similar to how structures are defined with the struct
keyword.
union tag_name {
data_type member1;
data_type member2;
/* ... */
data_type memberN;
};
Declaring Union Variables
You can declare a union variable in several ways:
// Method 1: Define and declare separately
union Data {
int i;
float f;
char str[20];
};
union Data data;
// Method 2: Define and declare together
union Data {
int i;
float f;
char str[20];
} data;
// Method 3: Anonymous union
union {
int i;
float f;
char str[20];
} data;
Memory Allocation in Unions
The key difference between unions and structures is memory allocation:
- In a structure, memory is allocated for all members individually.
- In a union, memory is allocated based on the size of the largest member.
For example:
union Data {
int i; // 4 bytes
float f; // 4 bytes
char str[20]; // 20 bytes
};
The total size of the union would be 20 bytes (the size of the largest member, str
).
Use sizeof()
operator to check the size of your union:
printf("Size of union: %lu bytes\n", sizeof(union Data));
Accessing Union Members
You access union members using the dot (.
) notation or arrow (->
) notation with pointers, just like structures:
union Data data;
data.i = 10; // Accessing using dot notation
printf("%d\n", data.i);
union Data *ptr = &data;
ptr->f = 220.5; // Accessing using arrow notation
printf("%.1f\n", ptr->f);
Important Characteristics of Unions
1. Shared Memory
The most significant aspect of unions is that all members share the same memory location:
union Data {
int i;
float f;
char str[20];
};
union Data data;
data.i = 10;
printf("data.i: %d\n", data.i);
data.f = 220.5;
printf("data.f: %.1f\n", data.f);
// The value of data.i is now corrupted because data.f overwrote
// the same memory location
printf("data.i: %d\n", data.i); // Will print a garbage value
2. One Active Member at a Time
Since all members share the same memory, only one member can contain a valid value at any time:
union Data data;
// Only one of these will contain the correct value
data.i = 10;
data.f = 220.5;
strcpy(data.str, "C Programming");
// The last assignment overwrites the memory, so only data.str is valid
printf("data.str: %s\n", data.str);
Use Cases for Unions
-
Memory Conservation: When you need to store one of several data types but will only use one at a time.
-
Type Punning: Viewing the same data in different ways.
union {
float f;
unsigned int i;
} u;
u.f = 1.5;
printf("Float: %f\n", u.f);
printf("As int bits: %08X\n", u.i); // View float bit pattern
- Variant Types: Creating variables that can hold different types of data.
typedef enum { INT_TYPE, FLOAT_TYPE, STRING_TYPE } ValueType;
typedef struct {
ValueType type;
union {
int i;
float f;
char str[20];
} value;
} Variant;
Variant v;
v.type = INT_TYPE;
v.value.i = 42;
Unions vs. Structures
Feature | Union | Structure |
---|---|---|
Memory allocation | Size of the largest member | Sum of all members' sizes |
Member values | Only one member active at a time | All members active simultaneously |
Usage | When only one property is needed at a time | When multiple properties are needed together |
Common Pitfalls
Accessing Inactive Members
Reading a union member after writing to a different member yields unpredictable results:
union Data data;
data.i = 10;
printf("data.f: %f\n", data.f); // Undefined behavior!
Forgetting About Byte Order
When using unions for type punning, be aware of endianness (byte order) on different platforms.
Practical Example: Tagged Union
A common pattern is to use a "tagged union" where an enum indicates which union member is currently active:
enum DataType { INTEGER, FLOAT, STRING };
struct Value {
enum DataType type;
union {
int i;
float f;
char str[20];
} data;
};
struct Value val;
val.type = FLOAT;
val.data.f = 3.14;
// Safe way to access the value
switch (val.type) {
case INTEGER:
printf("Integer: %d\n", val.data.i);
break;
case FLOAT:
printf("Float: %f\n", val.data.f);
break;
case STRING:
printf("String: %s\n", val.data.str);
break;
}
Summary
- Unions allow different data types to share memory space.
- The size of a union is determined by its largest member.
- Only one member of a union can be accessed safely at a time.
- Unions are useful for memory optimization and type punning.
- Always keep track of which union member is currently active to avoid unexpected behavior.
Accessing a union member after writing to a different member can lead to unpredictable results. Always be mindful of which member was last assigned a value.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)