C Unions
C - Unions (union)
Section titled “C - Unions (union)”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.
Defining a Union
Section titled “Defining a Union”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.
Size of a Union
Section titled “Size of a Union”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 bytesSize of int member: 4 bytesSize of float member: 4 bytesSize of string member: 20 bytesSize of double member: 8 bytesNotice 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.
Accessing Union Members
Section titled “Accessing Union Members”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!)Tagged Unions (Common Pattern)
Section titled “Tagged Unions (Common Pattern)”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 uniontypedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING} ValueType;
// The union itselftypedef 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 correctlyvoid 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: 123Type: Float, Value: 45.67Type: 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.