Introduction
Structures and unions are both user-defined data types in C that allow grouping different data types together. However, they differ fundamentally in how they store data in memory. Structures allocate separate memory for each member, while unions share the same memory location among all members. Understanding these differences is crucial for memory management, data organization, and choosing the appropriate data structure for specific use cases.
Key Concepts
Structure: User-defined data type where each member has its own memory location Union: User-defined data type where all members share the same memory location Memory Allocation: Structures use sum of member sizes, unions use size of largest member Data Access: Structures allow simultaneous access to all members, unions allow access to one member at a time Memory Layout: Structures have sequential memory layout, unions have overlapping memory layout Size Calculation: Structure size ≥ sum of member sizes, union size = size of largest member
Structure vs Union Comparison
1. Basic Definition and Declaration
#include <stdio.h>
#include <string.h>
// Structure definition
struct Student {
int id;
char name[50];
float marks;
char grade;
};
// Union definition
union Data {
int intValue;
float floatValue;
char charValue;
char stringValue[20];
};
int main() {
struct Student student;
union Data data;
printf("=== Structure vs Union Basics ===\n");
// Structure size and member sizes
printf("Structure Information:\n");
printf("Size of struct Student: %zu bytes\n", sizeof(struct Student));
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of char[50]: %zu bytes\n", sizeof(char[50]));
printf("Size of float: %zu bytes\n", sizeof(float));
printf("Size of char: %zu bytes\n", sizeof(char));
// Union size and member sizes
printf("\nUnion Information:\n");
printf("Size of union Data: %zu bytes\n", sizeof(union Data));
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of float: %zu bytes\n", sizeof(float));
printf("Size of char: %zu bytes\n", sizeof(char));
printf("Size of char[20]: %zu bytes\n", sizeof(char[20]));
return 0;
}
2. Memory Layout Demonstration
#include <stdio.h>
struct StructExample {
int a;
float b;
char c;
};
union UnionExample {
int a;
float b;
char c;
};
void printMemoryAddresses() {
struct StructExample s;
union UnionExample u;
printf("=== Memory Layout Comparison ===\n");
// Structure member addresses
printf("Structure Member Addresses:\n");
printf("Address of structure: %p\n", (void*)&s);
printf("Address of s.a (int): %p\n", (void*)&s.a);
printf("Address of s.b (float): %p\n", (void*)&s.b);
printf("Address of s.c (char): %p\n", (void*)&s.c);
printf("Address differences:\n");
printf("s.b - s.a = %ld bytes\n", (char*)&s.b - (char*)&s.a);
printf("s.c - s.b = %ld bytes\n", (char*)&s.c - (char*)&s.b);
// Union member addresses
printf("\nUnion Member Addresses:\n");
printf("Address of union: %p\n", (void*)&u);
printf("Address of u.a (int): %p\n", (void*)&u.a);
printf("Address of u.b (float): %p\n", (void*)&u.b);
printf("Address of u.c (char): %p\n", (void*)&u.c);
printf("Address differences:\n");
printf("u.b - u.a = %ld bytes\n", (char*)&u.b - (char*)&u.a);
printf("u.c - u.b = %ld bytes\n", (char*)&u.c - (char*)&u.b);
}
int main() {
printMemoryAddresses();
return 0;
}
3. Data Storage and Access Patterns
#include <stdio.h>
#include <string.h>
struct PersonStruct {
int age;
float salary;
char name[20];
char department;
};
union PersonUnion {
int age;
float salary;
char name[20];
char department;
};
int main() {
struct PersonStruct ps;
union PersonUnion pu;
printf("=== Data Storage and Access ===\n");
// Structure: All members can hold values simultaneously
printf("Structure - Simultaneous Storage:\n");
ps.age = 25;
ps.salary = 50000.0f;
strcpy(ps.name, "John Doe");
ps.department = 'E';
printf("Age: %d\n", ps.age);
printf("Salary: %.2f\n", ps.salary);
printf("Name: %s\n", ps.name);
printf("Department: %c\n", ps.department);
// Union: Only one member can hold meaningful value at a time
printf("\nUnion - Sequential Storage:\n");
pu.age = 30;
printf("After setting age to 30:\n");
printf("Age: %d\n", pu.age);
printf("Salary: %.2f (garbage)\n", pu.salary);
printf("Department: %c (garbage)\n", pu.department);
pu.salary = 60000.0f;
printf("\nAfter setting salary to 60000.0:\n");
printf("Age: %d (garbage)\n", pu.age);
printf("Salary: %.2f\n", pu.salary);
printf("Department: %c (garbage)\n", pu.department);
strcpy(pu.name, "Jane Smith");
printf("\nAfter setting name to 'Jane Smith':\n");
printf("Age: %d (garbage)\n", pu.age);
printf("Salary: %.2f (garbage)\n", pu.salary);
printf("Name: %s\n", pu.name);
printf("Department: %c (garbage)\n", pu.department);
return 0;
}
4. Practical Use Cases
#include <stdio.h>
#include <string.h>
// Structure use case: Complete data representation
struct BankAccount {
int accountNumber;
char holderName[50];
float balance;
char accountType; // 'S' for Savings, 'C' for Current
int isActive;
};
// Union use case: Data interpretation in different formats
union IPAddress {
unsigned int fullAddress; // 32-bit representation
unsigned char octets[4]; // 4 bytes representation
struct {
unsigned char octet1;
unsigned char octet2;
unsigned char octet3;
unsigned char octet4;
} parts;
};
// Union use case: Type-specific data storage
enum DataType { INT_TYPE, FLOAT_TYPE, STRING_TYPE, CHAR_TYPE };
struct VariableData {
enum DataType type;
union {
int intVal;
float floatVal;
char stringVal[50];
char charVal;
} value;
};
void demonstrateStructureUseCase() {
printf("=== Structure Use Case: Bank Account ===\n");
struct BankAccount account;
// Initialize all fields simultaneously
account.accountNumber = 123456789;
strcpy(account.holderName, "Alice Johnson");
account.balance = 15000.50f;
account.accountType = 'S';
account.isActive = 1;
// Access all fields simultaneously
printf("Account Details:\n");
printf("Account Number: %d\n", account.accountNumber);
printf("Holder Name: %s\n", account.holderName);
printf("Balance: $%.2f\n", account.balance);
printf("Account Type: %c\n", account.accountType);
printf("Status: %s\n", account.isActive ? "Active" : "Inactive");
}
void demonstrateUnionUseCase() {
printf("\n=== Union Use Case: IP Address Representation ===\n");
union IPAddress ip;
// Set IP as 32-bit integer
ip.fullAddress = 0xC0A80001; // 192.168.0.1 in hex
printf("IP Address Representations:\n");
printf("32-bit value: 0x%08X\n", ip.fullAddress);
printf("Dotted decimal: %d.%d.%d.%d\n",
ip.octets[3], ip.octets[2], ip.octets[1], ip.octets[0]);
// Variable data type example
printf("\n=== Union Use Case: Variable Data Types ===\n");
struct VariableData variables[3];
variables[0].type = INT_TYPE;
variables[0].value.intVal = 42;
variables[1].type = FLOAT_TYPE;
variables[1].value.floatVal = 3.14159f;
variables[2].type = STRING_TYPE;
strcpy(variables[2].value.stringVal, "Hello World");
for (int i = 0; i < 3; i++) {
printf("Variable %d: ", i + 1);
switch (variables[i].type) {
case INT_TYPE:
printf("Integer = %d\n", variables[i].value.intVal);
break;
case FLOAT_TYPE:
printf("Float = %.5f\n", variables[i].value.floatVal);
break;
case STRING_TYPE:
printf("String = %s\n", variables[i].value.stringVal);
break;
default:
printf("Unknown type\n");
}
}
}
int main() {
demonstrateStructureUseCase();
demonstrateUnionUseCase();
return 0;
}
5. Memory Efficiency Comparison
#include <stdio.h>
struct EmployeeStruct {
int empId;
double salary;
char name[50];
char department[20];
int experience;
};
union EmployeeUnion {
int empId;
double salary;
char name[50];
char department[20];
int experience;
};
void compareMemoryUsage() {
printf("=== Memory Efficiency Comparison ===\n");
printf("Employee Record:\n");
printf("Structure size: %zu bytes\n", sizeof(struct EmployeeStruct));
printf("Union size: %zu bytes\n", sizeof(union EmployeeUnion));
printf("Memory saved with union: %zu bytes\n",
sizeof(struct EmployeeStruct) - sizeof(union EmployeeUnion));
// Array comparison
printf("\nArray of 1000 records:\n");
printf("1000 Employee structures: %zu bytes\n",
1000 * sizeof(struct EmployeeStruct));
printf("1000 Employee unions: %zu bytes\n",
1000 * sizeof(union EmployeeUnion));
printf("Memory saved: %zu bytes (%.2f KB)\n",
1000 * (sizeof(struct EmployeeStruct) - sizeof(union EmployeeUnion)),
1000 * (sizeof(struct EmployeeStruct) - sizeof(union EmployeeUnion)) / 1024.0);
printf("Percentage saved: %.2f%%\n",
100.0 * (sizeof(struct EmployeeStruct) - sizeof(union EmployeeUnion)) / sizeof(struct EmployeeStruct));
}
int main() {
compareMemoryUsage();
return 0;
}
6. Common Pitfalls and Best Practices
#include <stdio.h>
#include <string.h>
// Good practice: Tag union with type information
enum ValueType { TYPE_INT, TYPE_FLOAT, TYPE_STRING };
struct TaggedUnion {
enum ValueType type;
union {
int intValue;
float floatValue;
char stringValue[20];
} data;
};
void demonstratePitfalls() {
printf("=== Common Pitfalls ===\n");
union Number {
int intVal;
float floatVal;
} num;
// Pitfall: Assuming union retains all values
printf("Pitfall - Value Overwriting:\n");
num.intVal = 100;
printf("Set int to 100: %d\n", num.intVal);
num.floatVal = 3.14f;
printf("Set float to 3.14: %.2f\n", num.floatVal);
printf("Int value now: %d (corrupted!)\n", num.intVal);
}
void demonstrateBestPractices() {
printf("\n=== Best Practices ===\n");
// Best Practice: Use tagged unions
struct TaggedUnion values[3];
values[0].type = TYPE_INT;
values[0].data.intValue = 42;
values[1].type = TYPE_FLOAT;
values[1].data.floatValue = 3.14f;
values[2].type = TYPE_STRING;
strcpy(values[2].data.stringValue, "Hello");
printf("Tagged Union Usage:\n");
for (int i = 0; i < 3; i++) {
printf("Value %d: ", i + 1);
switch (values[i].type) {
case TYPE_INT:
printf("Int = %d\n", values[i].data.intValue);
break;
case TYPE_FLOAT:
printf("Float = %.2f\n", values[i].data.floatValue);
break;
case TYPE_STRING:
printf("String = %s\n", values[i].data.stringValue);
break;
}
}
}
int main() {
demonstratePitfalls();
demonstrateBestPractices();
return 0;
}
Key Differences Summary
| Feature | Structure | Union |
|---|---|---|
| Memory Allocation | Each member gets separate memory | All members share same memory |
| Size | Sum of all member sizes (+ padding) | Size of largest member (+ padding) |
| Data Access | All members accessible simultaneously | Only one member valid at a time |
| Use Case | Complete data representation | Memory-efficient variant storage |
| Initialization | Can initialize all members | Can initialize only one member |
| Memory Efficiency | Higher memory usage | Lower memory usage |
| Data Integrity | All data preserved | Data overwritten on reassignment |
When to Use Each
Use Structures When:
- You need to store multiple related data items together
- All data members need to be accessed simultaneously
- Building complex data types like records or objects
- Memory usage is not the primary concern
- Data integrity and completeness are important
Use Unions When:
- Memory efficiency is critical
- Only one data type is needed at a time
- Implementing variant data types
- Working with different interpretations of same data
- Building space-efficient data structures
Important Points
- Memory layout differs fundamentally between structures and unions
- All structure members can hold valid values simultaneously
- Only one union member can hold a valid value at any given time
- Union size equals the size of its largest member plus padding
- Structure size equals sum of member sizes plus padding
- Unions save memory when only one data type is needed at a time
- Tagged unions combine unions with type identification for safe usage
- Proper initialization is crucial for both structures and unions
Best Practices
- Use tagged unions to track which member is currently valid
- Initialize unions properly using designated initializers
- Document union usage clearly in code comments
- Choose structures for complete data representation
- Choose unions for memory-efficient variant storage
- Avoid accessing invalid union members
- Test memory layout on target platforms
- Consider alignment and padding requirements
Summary
Structures and unions serve different purposes in C programming. Structures excel at organizing related data with simultaneous access to all members, making them ideal for complex data representation. Unions provide memory-efficient storage for mutually exclusive data types, making them valuable for space-constrained applications and variant data handling. Understanding their differences in memory allocation, access patterns, and use cases is essential for choosing the appropriate data structure for specific programming requirements.