Introduction
Union declaration in C creates a user-defined data type that allows storing different data types in the same memory location. Unlike structures where each member has its own memory space, all members of a union share the same memory location. Only one member can hold a value at any given time, making unions useful for memory-efficient programming and type conversion.
Key Concepts
Union: User-defined type sharing memory among all members Memory Sharing: All members occupy the same memory location Size: Union size equals the size of its largest member Active Member: Only one member contains valid data at a time Type Punning: Interpreting same memory as different data types Variant Type: Union can represent multiple types in one variable
Basic Union Declaration
1. Simple Union Declaration
#include <stdio.h>
#include <string.h>
// Basic union declaration
union Data {
int intValue;
float floatValue;
char charValue;
char stringValue[20];
};
// Union with different data types
union Number {
int integer;
float decimal;
double precision;
};
int main() {
printf("=== Basic Union Declaration ===\n");
union Data data;
union Number num;
// Store integer value
data.intValue = 42;
printf("Integer value: %d\n", data.intValue);
printf("Union size: %zu bytes\n", sizeof(data));
// Store float value (overwrites integer)
data.floatValue = 3.14;
printf("Float value: %.2f\n", data.floatValue);
printf("Integer value now: %d (corrupted)\n", data.intValue);
// Store character value
data.charValue = 'A';
printf("Character value: %c\n", data.charValue);
// Store string value
strcpy(data.stringValue, "Hello Union");
printf("String value: %s\n", data.stringValue);
// Demonstrate memory sharing
printf("\nMemory addresses:\n");
printf("intValue address: %p\n", (void*)&data.intValue);
printf("floatValue address: %p\n", (void*)&data.floatValue);
printf("charValue address: %p\n", (void*)&data.charValue);
printf("stringValue address: %p\n", (void*)&data.stringValue);
return 0;
}
2. Union vs Structure Size Comparison
#include <stdio.h>
// Structure with same members
struct DataStruct {
int intValue;
float floatValue;
char charValue;
char stringValue[20];
};
// Union with same members
union DataUnion {
int intValue;
float floatValue;
char charValue;
char stringValue[20];
};
int main() {
printf("=== Union vs Structure Size ===\n");
struct DataStruct ds;
union DataUnion du;
printf("Structure size: %zu bytes\n", sizeof(ds));
printf("Union size: %zu bytes\n", sizeof(du));
printf("\nStructure member sizes:\n");
printf("int: %zu bytes\n", sizeof(ds.intValue));
printf("float: %zu bytes\n", sizeof(ds.floatValue));
printf("char: %zu bytes\n", sizeof(ds.charValue));
printf("char[20]: %zu bytes\n", sizeof(ds.stringValue));
printf("\nUnion member sizes (same as above):\n");
printf("int: %zu bytes\n", sizeof(du.intValue));
printf("float: %zu bytes\n", sizeof(du.floatValue));
printf("char: %zu bytes\n", sizeof(du.charValue));
printf("char[20]: %zu bytes\n", sizeof(du.stringValue));
printf("\nMemory usage comparison:\n");
printf("Structure total: %zu bytes (sum of all members + padding)\n", sizeof(ds));
printf("Union total: %zu bytes (size of largest member)\n", sizeof(du));
return 0;
}
Practical Union Applications
1. Type Conversion and Bit Manipulation
#include <stdio.h>
// Union for examining float representation
union FloatBits {
float floatValue;
unsigned int bits;
struct {
unsigned int mantissa : 23;
unsigned int exponent : 8;
unsigned int sign : 1;
} parts;
};
// Union for byte access
union IntBytes {
int value;
unsigned char bytes[4];
};
// Union for endianness testing
union EndianTest {
int value;
char bytes[4];
};
void displayFloatBits(float f) {
union FloatBits fb;
fb.floatValue = f;
printf("Float: %.6f\n", fb.floatValue);
printf("Bits: 0x%08X\n", fb.bits);
printf("Sign: %u, Exponent: %u, Mantissa: %u\n",
fb.parts.sign, fb.parts.exponent, fb.parts.mantissa);
}
void displayIntBytes(int value) {
union IntBytes ib;
ib.value = value;
printf("Integer: %d (0x%08X)\n", ib.value, ib.value);
printf("Bytes: ");
for (int i = 0; i < 4; i++) {
printf("0x%02X ", ib.bytes[i]);
}
printf("\n");
}
int main() {
printf("=== Type Conversion and Bit Manipulation ===\n");
// Float bit representation
printf("Float bit representation:\n");
displayFloatBits(3.14159f);
displayFloatBits(-1.0f);
displayFloatBits(0.0f);
printf("\nInteger byte representation:\n");
displayIntBytes(0x12345678);
displayIntBytes(1000);
// Endianness test
union EndianTest et;
et.value = 0x12345678;
printf("\nEndianness test:\n");
printf("Value: 0x%08X\n", et.value);
printf("First byte: 0x%02X\n", (unsigned char)et.bytes[0]);
if (et.bytes[0] == 0x78) {
printf("System is Little Endian\n");
} else if (et.bytes[0] == 0x12) {
printf("System is Big Endian\n");
}
return 0;
}
2. Variant Data Types
#include <stdio.h>
#include <string.h>
// Enumeration for data types
typedef enum {
TYPE_INT,
TYPE_FLOAT,
TYPE_STRING,
TYPE_BOOL
} DataType;
// Variant structure using union
typedef struct {
DataType type;
union {
int intValue;
float floatValue;
char stringValue[50];
int boolValue; // 0 for false, 1 for true
} data;
} Variant;
// Function to create variant with integer
Variant createIntVariant(int value) {
Variant v;
v.type = TYPE_INT;
v.data.intValue = value;
return v;
}
// Function to create variant with float
Variant createFloatVariant(float value) {
Variant v;
v.type = TYPE_FLOAT;
v.data.floatValue = value;
return v;
}
// Function to create variant with string
Variant createStringVariant(const char* value) {
Variant v;
v.type = TYPE_STRING;
strncpy(v.data.stringValue, value, sizeof(v.data.stringValue) - 1);
v.data.stringValue[sizeof(v.data.stringValue) - 1] = '\0';
return v;
}
// Function to create variant with boolean
Variant createBoolVariant(int value) {
Variant v;
v.type = TYPE_BOOL;
v.data.boolValue = value ? 1 : 0;
return v;
}
// Function to display variant
void displayVariant(Variant v) {
switch (v.type) {
case TYPE_INT:
printf("Integer: %d\n", v.data.intValue);
break;
case TYPE_FLOAT:
printf("Float: %.2f\n", v.data.floatValue);
break;
case TYPE_STRING:
printf("String: %s\n", v.data.stringValue);
break;
case TYPE_BOOL:
printf("Boolean: %s\n", v.data.boolValue ? "true" : "false");
break;
default:
printf("Unknown type\n");
}
}
int main() {
printf("=== Variant Data Types ===\n");
// Create array of variants
Variant values[5];
values[0] = createIntVariant(42);
values[1] = createFloatVariant(3.14159);
values[2] = createStringVariant("Hello, World!");
values[3] = createBoolVariant(1);
values[4] = createBoolVariant(0);
// Display all variants
printf("Variant array contents:\n");
for (int i = 0; i < 5; i++) {
printf("Value %d: ", i);
displayVariant(values[i]);
}
// Demonstrate variant size efficiency
printf("\nMemory usage:\n");
printf("Variant size: %zu bytes\n", sizeof(Variant));
printf("Individual sizes: int=%zu, float=%zu, string=%zu\n",
sizeof(int), sizeof(float), sizeof(char[50]));
return 0;
}
3. Network Protocol Parsing
#include <stdio.h>
#include <stdint.h>
// IP address representation
union IPAddress {
uint32_t address;
struct {
uint8_t octet1;
uint8_t octet2;
uint8_t octet3;
uint8_t octet4;
} octets;
uint8_t bytes[4];
};
// Network packet header
union PacketHeader {
uint32_t raw;
struct {
uint32_t version : 4;
uint32_t headerLength : 4;
uint32_t typeOfService : 8;
uint32_t totalLength : 16;
} fields;
};
// Color representation
union Color {
uint32_t value;
struct {
uint8_t blue;
uint8_t green;
uint8_t red;
uint8_t alpha;
} rgba;
uint8_t components[4];
};
void displayIPAddress(union IPAddress ip) {
printf("IP Address: %u.%u.%u.%u\n",
ip.octets.octet1, ip.octets.octet2,
ip.octets.octet3, ip.octets.octet4);
printf("Raw value: 0x%08X\n", ip.address);
printf("Bytes: %u %u %u %u\n",
ip.bytes[0], ip.bytes[1], ip.bytes[2], ip.bytes[3]);
}
void displayPacketHeader(union PacketHeader header) {
printf("Packet Header: 0x%08X\n", header.raw);
printf("Version: %u\n", header.fields.version);
printf("Header Length: %u\n", header.fields.headerLength);
printf("Type of Service: %u\n", header.fields.typeOfService);
printf("Total Length: %u\n", header.fields.totalLength);
}
void displayColor(union Color color) {
printf("Color: 0x%08X\n", color.value);
printf("RGBA: (%u, %u, %u, %u)\n",
color.rgba.red, color.rgba.green, color.rgba.blue, color.rgba.alpha);
printf("Components: [%u, %u, %u, %u]\n",
color.components[0], color.components[1],
color.components[2], color.components[3]);
}
int main() {
printf("=== Network Protocol Parsing ===\n");
// IP address example
union IPAddress ip;
ip.address = 0xC0A80101; // 192.168.1.1
printf("IP Address Analysis:\n");
displayIPAddress(ip);
// Set IP by octets
ip.octets.octet1 = 10;
ip.octets.octet2 = 0;
ip.octets.octet3 = 0;
ip.octets.octet4 = 1;
printf("\nModified IP Address:\n");
displayIPAddress(ip);
// Packet header example
union PacketHeader header;
header.raw = 0x45000028; // IPv4 header example
printf("\nPacket Header Analysis:\n");
displayPacketHeader(header);
// Color example
union Color color;
color.value = 0xFF00FF80; // Purple with transparency
printf("\nColor Analysis:\n");
displayColor(color);
return 0;
}
Union with typedef
Simplifying Union Usage
#include <stdio.h>
#include <string.h>
// Union with typedef for cleaner syntax
typedef union {
int asInt;
float asFloat;
char asChar;
unsigned int asUnsigned;
} Value;
// Tagged union for safe type handling
typedef enum {
TAG_INT,
TAG_FLOAT,
TAG_STRING
} ValueTag;
typedef struct {
ValueTag tag;
union {
int intVal;
float floatVal;
char stringVal[32];
} value;
} TaggedValue;
// Function to create tagged integer
TaggedValue makeInt(int val) {
TaggedValue tv;
tv.tag = TAG_INT;
tv.value.intVal = val;
return tv;
}
// Function to create tagged float
TaggedValue makeFloat(float val) {
TaggedValue tv;
tv.tag = TAG_FLOAT;
tv.value.floatVal = val;
return tv;
}
// Function to create tagged string
TaggedValue makeString(const char* val) {
TaggedValue tv;
tv.tag = TAG_STRING;
strncpy(tv.value.stringVal, val, sizeof(tv.value.stringVal) - 1);
tv.value.stringVal[sizeof(tv.value.stringVal) - 1] = '\0';
return tv;
}
// Function to safely print tagged value
void printTaggedValue(TaggedValue tv) {
switch (tv.tag) {
case TAG_INT:
printf("Integer: %d\n", tv.value.intVal);
break;
case TAG_FLOAT:
printf("Float: %.2f\n", tv.value.floatVal);
break;
case TAG_STRING:
printf("String: %s\n", tv.value.stringVal);
break;
default:
printf("Unknown type\n");
}
}
int main() {
printf("=== Union with typedef ===\n");
// Simple union usage
Value val;
val.asInt = 42;
printf("As integer: %d\n", val.asInt);
printf("As float: %.2f\n", val.asFloat); // Interpretation, not conversion
printf("As unsigned: %u\n", val.asUnsigned);
val.asFloat = 3.14159f;
printf("\nAfter setting float:\n");
printf("As integer: %d\n", val.asInt); // Now invalid
printf("As float: %.2f\n", val.asFloat); // Valid
// Tagged union usage
printf("\nTagged Union (Safe):\n");
TaggedValue values[3];
values[0] = makeInt(100);
values[1] = makeFloat(2.71828f);
values[2] = makeString("Hello");
for (int i = 0; i < 3; i++) {
printf("Value %d: ", i);
printTaggedValue(values[i]);
}
return 0;
}
Memory Layout and Alignment
Understanding Union Memory Layout
#include <stdio.h>
#include <stddef.h>
// Union with different sized members
union MixedUnion {
char c;
short s;
int i;
long l;
float f;
double d;
};
// Union with structure member
struct Point {
int x, y;
};
union ShapeData {
int radius; // For circle
struct Point dimensions; // For rectangle
float sideLength; // For square
};
// Union with array
union ArrayUnion {
int singleInt;
int intArray[4];
char charArray[16];
};
int main() {
printf("=== Union Memory Layout ===\n");
union MixedUnion mu;
union ShapeData sd;
union ArrayUnion au;
// Display union sizes
printf("Union sizes:\n");
printf("MixedUnion: %zu bytes\n", sizeof(mu));
printf("ShapeData: %zu bytes\n", sizeof(sd));
printf("ArrayUnion: %zu bytes\n", sizeof(au));
// Display member sizes
printf("\nMixedUnion member sizes:\n");
printf("char: %zu bytes\n", sizeof(mu.c));
printf("short: %zu bytes\n", sizeof(mu.s));
printf("int: %zu bytes\n", sizeof(mu.i));
printf("long: %zu bytes\n", sizeof(mu.l));
printf("float: %zu bytes\n", sizeof(mu.f));
printf("double: %zu bytes\n", sizeof(mu.d));
// Show memory addresses are the same
printf("\nMixedUnion member addresses:\n");
printf("char address: %p\n", (void*)&mu.c);
printf("short address: %p\n", (void*)&mu.s);
printf("int address: %p\n", (void*)&mu.i);
printf("double address: %p\n", (void*)&mu.d);
// Demonstrate overlapping memory
au.singleInt = 0x12345678;
printf("\nArray union demonstration:\n");
printf("Single int: 0x%08X\n", au.singleInt);
printf("Int array: ");
for (int i = 0; i < 4; i++) {
printf("0x%08X ", au.intArray[i]);
}
printf("\n");
printf("Char array: ");
for (int i = 0; i < 16; i++) {
printf("%02X ", (unsigned char)au.charArray[i]);
}
printf("\n");
return 0;
}
Union Safety and Best Practices
Safe Union Usage Patterns
#include <stdio.h>
#include <string.h>
// Enumeration for union member types
typedef enum {
DATA_EMPTY,
DATA_INTEGER,
DATA_FLOAT,
DATA_STRING
} DataType;
// Safe union with type tracking
typedef struct {
DataType type;
union {
int intValue;
float floatValue;
char stringValue[50];
} data;
} SafeUnion;
// Function to safely set integer
void setInteger(SafeUnion* su, int value) {
su->type = DATA_INTEGER;
su->data.intValue = value;
}
// Function to safely set float
void setFloat(SafeUnion* su, float value) {
su->type = DATA_FLOAT;
su->data.floatValue = value;
}
// Function to safely set string
void setString(SafeUnion* su, const char* value) {
su->type = DATA_STRING;
strncpy(su->data.stringValue, value, sizeof(su->data.stringValue) - 1);
su->data.stringValue[sizeof(su->data.stringValue) - 1] = '\0';
}
// Function to safely get integer
int getInteger(const SafeUnion* su, int* result) {
if (su->type == DATA_INTEGER) {
*result = su->data.intValue;
return 1; // Success
}
return 0; // Wrong type
}
// Function to safely get float
int getFloat(const SafeUnion* su, float* result) {
if (su->type == DATA_FLOAT) {
*result = su->data.floatValue;
return 1; // Success
}
return 0; // Wrong type
}
// Function to safely get string
int getString(const SafeUnion* su, char* result, size_t maxSize) {
if (su->type == DATA_STRING) {
strncpy(result, su->data.stringValue, maxSize - 1);
result[maxSize - 1] = '\0';
return 1; // Success
}
return 0; // Wrong type
}
// Function to display safe union
void displaySafeUnion(const SafeUnion* su) {
switch (su->type) {
case DATA_EMPTY:
printf("Empty union\n");
break;
case DATA_INTEGER:
printf("Integer: %d\n", su->data.intValue);
break;
case DATA_FLOAT:
printf("Float: %.2f\n", su->data.floatValue);
break;
case DATA_STRING:
printf("String: %s\n", su->data.stringValue);
break;
default:
printf("Unknown type\n");
}
}
int main() {
printf("=== Safe Union Usage ===\n");
SafeUnion su = {DATA_EMPTY, {0}}; // Initialize empty
// Safely set and access values
setInteger(&su, 42);
displaySafeUnion(&su);
setFloat(&su, 3.14159f);
displaySafeUnion(&su);
setString(&su, "Hello, Safe Union!");
displaySafeUnion(&su);
// Demonstrate safe access
int intResult;
float floatResult;
char stringResult[100];
printf("\nSafe access attempts:\n");
if (getInteger(&su, &intResult)) {
printf("Got integer: %d\n", intResult);
} else {
printf("Cannot get integer (wrong type)\n");
}
if (getFloat(&su, &floatResult)) {
printf("Got float: %.2f\n", floatResult);
} else {
printf("Cannot get float (wrong type)\n");
}
if (getString(&su, stringResult, sizeof(stringResult))) {
printf("Got string: %s\n", stringResult);
} else {
printf("Cannot get string (wrong type)\n");
}
return 0;
}
Important Points
- Memory sharing: All union members occupy the same memory location
- Size efficiency: Union size equals largest member size
- One active member: Only one member contains valid data at a time
- Type safety: Programmer responsible for tracking active member
- Bit patterns: Members can reinterpret same bit pattern differently
- Alignment: Union aligned to most restrictive member requirement
- Initialization: Only first member can be initialized in declaration
- Portability: Behavior may vary across different systems
Best Practices
- Track active member using separate type indicator
- Use tagged unions for type safety
- Initialize unions explicitly after declaration
- Avoid accessing inactive members
- Document intended usage for each union
- Consider alternatives like void pointers or inheritance
- Test on target platform for portability
- Use unions sparingly and only when memory efficiency is critical
Summary
Union declaration in C creates memory-efficient data types where all members share the same memory location. Unions are useful for type conversion, variant data types, and memory-constrained applications. However, they require careful handling to maintain type safety and avoid accessing invalid data. Tagged unions provide a safer approach by tracking the active member type.
Part of BCA Programming with C Course (UGCOA22J201)