Skip to content

C Unions

A union is a user-defined data type, similar to a struct, but with a key difference: all members of a union share the same memory location. This means a union variable can hold a value for only one of its members at any given time.

Unions are used when you need to store different types of data in the same memory space, typically because only one type is relevant at a specific moment. This can save memory compared to a structure where each member gets its own space.

The syntax for defining a union is very similar to defining a structure, using the union keyword.

union [union_tag] {
type member1_name;
type member2_name;
// ... other members
} [optional_variable_declarations];

Example: A union to hold either an integer, a float, or a character array.

// Define the union named 'DataValue'
union DataValue {
int i; // Integer value
float f; // Floating-point value
char str[20]; // String value (character array)
};

A variable of type union DataValue can store an int, OR a float, OR a char[20], but not simultaneously.

The size of a union variable is determined by the size of its largest member. The compiler allocates enough memory to accommodate the biggest member, plus any potential padding needed for alignment.

#include <stdio.h>
#include <string.h> // Included for potential string operations later
union DataValue {
int i; // Typically 4 bytes
float f; // Typically 4 bytes
char str[20]; // 20 bytes
double d; // Typically 8 bytes
};
int main(void) {
union DataValue data;
// The size will be at least sizeof(char[20]) = 20 bytes,
// possibly more due to alignment requirements for 'double'.
printf("Memory size occupied by union DataValue: %zu bytes\n", sizeof(data));
printf("Size of int member: %zu bytes\n", sizeof(data.i));
printf("Size of float member: %zu bytes\n", sizeof(data.f));
printf("Size of string member: %zu bytes\n", sizeof(data.str));
printf("Size of double member: %zu bytes\n", sizeof(data.d));
return 0;
}

Example Output (on a system where double requires 8-byte alignment):

Memory size occupied by union DataValue: 24 bytes
Size of int member: 4 bytes
Size of float member: 4 bytes
Size of string member: 20 bytes
Size of double member: 8 bytes

Notice the size is 24, not 20, because the double member likely requires 8-byte alignment, forcing the total size to be a multiple of 8.

You access union members using the same member access operator (.) as structures.

Syntax: union_variable.member_name

Crucial Rule: You should only read from the member that was most recently written to. Reading from a different member results in accessing the raw bytes of the last written value, reinterpreted according to the type you are reading, which usually leads to garbage values or undefined behavior.

Example: Correct and Incorrect Usage

#include <stdio.h>
#include <string.h>
union DataValue {
int i;
float f;
char str[20];
};
int main(void) {
union DataValue data;
// --- Scenario 1: Writing and reading the same member (CORRECT) ---
printf("--- Correct Usage ---\n");
data.i = 10;
printf("data.i: %d (after writing to i)\n", data.i); // OK
data.f = 220.5f;
printf("data.f: %.1f (after writing to f)\n", data.f); // OK
strncpy(data.str, "C Programming", sizeof(data.str) - 1);
data.str[sizeof(data.str) - 1] = '\0';
printf("data.str: %s (after writing to str)\n", data.str); // OK
// --- Scenario 2: Writing one member, reading another (INCORRECT) ---
printf("\n--- Incorrect Usage ---\n");
data.i = 1094795585; // An integer value (binary representation matters)
// Now try reading data.f and data.str - they will contain garbage
// because the memory holds the bits for the integer 1094795585.
printf("data.i: %d (last write was to i)\n", data.i);
printf("data.f: %f (reading f after writing i - GARBAGE!)\n", data.f);
printf("data.str: %s (reading str after writing i - GARBAGE!)\n", data.str);
// Example: Write float, read int
data.f = 99.9f;
printf("\ndata.f: %f (last write was to f)\n", data.f);
printf("data.i: %d (reading i after writing f - GARBAGE!)\n", data.i);
return 0;
}

Example Output (garbage values will vary):

--- Correct Usage ---
data.i: 10 (after writing to i)
data.f: 220.5 (after writing to f)
data.str: C Programming (after writing to str)
--- Incorrect Usage ---
data.i: 1094795585 (last write was to i)
data.f: 10.000000 (reading f after writing i - GARBAGE!)
data.str: C (reading str after writing i - GARBAGE!)
data.f: 99.900002 (last write was to f)
data.i: 1120403458 (reading i after writing f - GARBAGE!)

Because you need to know which member is currently valid, unions are often used within a structure along with a ‘tag’ member (usually an enum or int) that indicates the type of the currently stored value.

#include <stdio.h>
#include <string.h>
// Enum to identify the type stored in the union
typedef enum {
TYPE_INT,
TYPE_FLOAT,
TYPE_STRING
} ValueType;
// The union itself
typedef union {
int i;
float f;
char str[50];
} ValueUnion;
// Structure containing the tag and the union (Tagged Union)
typedef struct {
ValueType type; // Tag indicating which member is active
ValueUnion value; // The union holding the actual data
} TaggedValue;
// Function to print the tagged value correctly
void print_tagged_value(TaggedValue tv) {
printf("Type: ");
switch (tv.type) {
case TYPE_INT:
printf("Integer, Value: %d\n", tv.value.i);
break;
case TYPE_FLOAT:
printf("Float, Value: %.2f\n", tv.value.f);
break;
case TYPE_STRING:
printf("String, Value: \"%s\"\n", tv.value.str);
break;
default:
printf("Unknown\n");
break;
}
}
int main(void) {
TaggedValue val1, val2, val3;
// Store and print an integer
val1.type = TYPE_INT;
val1.value.i = 123;
print_tagged_value(val1);
// Store and print a float
val2.type = TYPE_FLOAT;
val2.value.f = 45.67f;
print_tagged_value(val2);
// Store and print a string
val3.type = TYPE_STRING;
strncpy(val3.value.str, "Hello Union!", sizeof(val3.value.str) - 1);
val3.value.str[sizeof(val3.value.str) - 1] = '\0';
print_tagged_value(val3);
return 0;
}

Output:

Type: Integer, Value: 123
Type: Float, Value: 45.67
Type: String, Value: "Hello Union!"

Tagged unions are a safe and common way to handle variables that can hold different types of data at different times, ensuring you always interpret the stored value correctly.