Unions and Enumerations in C
Unions and enumerations are advanced data types that provide memory-efficient storage and improve code readability.
"Unions allow different data types to share the same memory location, while enumerations provide named integer constants for better code organization."
Enumerations (enum)
Enumerations allow you to define a set of named integer constants, making code more readable and maintainable.
Basic enum Declaration
// Basic enumeration
enum Color {
RED, // 0
GREEN, // 1
BLUE // 2
};
// Enumeration with custom values
enum Status {
SUCCESS = 0,
ERROR = -1,
PENDING = 1,
TIMEOUT = 2
};
// Enumeration with explicit values
enum Month {
JAN = 1,
FEB = 2,
MAR = 3,
APR = 4,
MAY = 5,
JUN = 6,
JUL = 7,
AUG = 8,
SEP = 9,
OCT = 10,
NOV = 11,
DEC = 12
};
Using enum Variables
#include <stdio.h>
enum Color {
RED,
GREEN,
BLUE
};
int main() {
enum Color favorite = GREEN;
printf("Favorite color code: %d\n", favorite);
// Using in switch statement
switch (favorite) {
case RED:
printf("You chose red!\n");
break;
case GREEN:
printf("You chose green!\n");
break;
case BLUE:
printf("You chose blue!\n");
break;
}
return 0;
}
typedef with enum
#include <stdio.h>
// Without typedef
enum Weekday { MON, TUE, WED, THU, FRI, SAT, SUN };
enum Weekday today = WED;
// With typedef (more convenient)
typedef enum {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
} Weekday;
int main() {
Weekday today = WEDNESDAY;
printf("Today is: ");
switch (today) {
case MONDAY: printf("Monday\n"); break;
case TUESDAY: printf("Tuesday\n"); break;
case WEDNESDAY: printf("Wednesday\n"); break;
case THURSDAY: printf("Thursday\n"); break;
case FRIDAY: printf("Friday\n"); break;
case SATURDAY: printf("Saturday\n"); break;
case SUNDAY: printf("Sunday\n"); break;
}
return 0;
}
Unions
Unions allow different data types to share the same memory location, providing memory-efficient storage for variables that are never used simultaneously.
Basic Union Declaration
// Basic union
union Data {
int i;
float f;
char str[20];
};
// Union with typedef
typedef union {
int intValue;
float floatValue;
char charValue;
} Value;
Union Memory Sharing
#include <stdio.h>
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
printf("Memory size occupied by union: %zu bytes\n", sizeof(data));
// Store integer
data.i = 42;
printf("data.i: %d\n", data.i);
// Store float (overwrites integer)
data.f = 3.14f;
printf("data.f: %.2f\n", data.f);
printf("data.i (garbage): %d\n", data.i); // Undefined behavior
// Store string (overwrites float)
strcpy(data.str, "Hello");
printf("data.str: %s\n", data.str);
return 0;
}
Union with Structures
#include <stdio.h>
#include <string.h>
typedef enum { INT_TYPE, FLOAT_TYPE, STRING_TYPE } DataType;
typedef union {
int intValue;
float floatValue;
char stringValue[50];
} DataValue;
typedef struct {
DataType type;
DataValue value;
} VariantData;
void printData(VariantData data) {
switch (data.type) {
case INT_TYPE:
printf("Integer: %d\n", data.value.intValue);
break;
case FLOAT_TYPE:
printf("Float: %.2f\n", data.value.floatValue);
break;
case STRING_TYPE:
printf("String: %s\n", data.value.stringValue);
break;
}
}
int main() {
VariantData data1, data2, data3;
// Store integer
data1.type = INT_TYPE;
data1.value.intValue = 42;
// Store float
data2.type = FLOAT_TYPE;
data2.value.floatValue = 3.14159f;
// Store string
data3.type = STRING_TYPE;
strcpy(data3.value.stringValue, "Hello, World!");
printData(data1);
printData(data2);
printData(data3);
return 0;
}
Practical Applications
1. Device Register Access
// Example: Accessing hardware registers
typedef union {
struct {
unsigned int enable : 1;
unsigned int mode : 2;
unsigned int speed : 3;
unsigned int reserved : 26;
} bits;
unsigned int value;
} DeviceRegister;
int main() {
DeviceRegister reg;
// Set register value
reg.value = 0x00000015; // Binary: 00010101
printf("Enable: %d\n", reg.bits.enable); // 1
printf("Mode: %d\n", reg.bits.mode); // 2 (binary: 10)
printf("Speed: %d\n", reg.bits.speed); // 5 (binary: 101)
// Modify individual bits
reg.bits.enable = 0;
reg.bits.speed = 3;
printf("New register value: 0x%08X\n", reg.value);
return 0;
}
2. Network Packet Handling
#include <stdio.h>
#include <string.h>
typedef enum {
PACKET_DATA,
PACKET_COMMAND,
PACKET_RESPONSE
} PacketType;
typedef union {
struct {
char data[100];
int length;
} data_packet;
struct {
int command_id;
char parameters[96];
} command_packet;
struct {
int response_code;
char message[96];
} response_packet;
} PacketData;
typedef struct {
PacketType type;
PacketData data;
} NetworkPacket;
void process_packet(NetworkPacket packet) {
switch (packet.type) {
case PACKET_DATA:
printf("Data packet: %.*s (length: %d)\n",
packet.data.data_packet.length,
packet.data.data_packet.data,
packet.data.data_packet.length);
break;
case PACKET_COMMAND:
printf("Command packet: ID=%d, Params=%s\n",
packet.data.command_packet.command_id,
packet.data.command_packet.parameters);
break;
case PACKET_RESPONSE:
printf("Response packet: Code=%d, Message=%s\n",
packet.data.response_packet.response_code,
packet.data.response_packet.message);
break;
}
}
int main() {
NetworkPacket packet1, packet2;
// Data packet
packet1.type = PACKET_DATA;
strcpy(packet1.data.data_packet.data, "Hello Network!");
packet1.data.data_packet.length = strlen(packet1.data.data_packet.data);
// Command packet
packet2.type = PACKET_COMMAND;
packet2.data.command_packet.command_id = 1001;
strcpy(packet2.data.command_packet.parameters, "restart");
process_packet(packet1);
process_packet(packet2);
return 0;
}
3. Mathematical Operations with Different Types
#include <stdio.h>
#include <math.h>
typedef enum {
INTEGER,
FLOAT,
COMPLEX
} NumberType;
typedef union {
int int_val;
float float_val;
struct {
float real;
float imag;
} complex_val;
} NumberValue;
typedef struct {
NumberType type;
NumberValue value;
} Number;
Number add_numbers(Number a, Number b) {
Number result;
if (a.type == INTEGER && b.type == INTEGER) {
result.type = INTEGER;
result.value.int_val = a.value.int_val + b.value.int_val;
} else if (a.type == FLOAT && b.type == FLOAT) {
result.type = FLOAT;
result.value.float_val = a.value.float_val + b.value.float_val;
} else {
// Convert to float for mixed operations
result.type = FLOAT;
float val_a = (a.type == INTEGER) ? a.value.int_val : a.value.float_val;
float val_b = (b.type == INTEGER) ? b.value.int_val : b.value.float_val;
result.value.float_val = val_a + val_b;
}
return result;
}
void print_number(Number n) {
switch (n.type) {
case INTEGER:
printf("%d", n.value.int_val);
break;
case FLOAT:
printf("%.2f", n.value.float_val);
break;
case COMPLEX:
printf("%.2f + %.2fi", n.value.complex_val.real, n.value.complex_val.imag);
break;
}
}
int main() {
Number a = {INTEGER, {.int_val = 5}};
Number b = {FLOAT, {.float_val = 3.5f}};
Number sum = add_numbers(a, b);
printf("Result: ");
print_number(sum);
printf("\n");
return 0;
}
4. State Machine Implementation
#include <stdio.h>
typedef enum {
STATE_IDLE,
STATE_RUNNING,
STATE_PAUSED,
STATE_ERROR,
STATE_FINISHED
} ProcessState;
typedef union {
struct {
int task_id;
char description[50];
} idle_info;
struct {
float progress;
int elapsed_time;
} running_info;
struct {
int pause_reason;
int resume_time;
} paused_info;
struct {
int error_code;
char error_message[100];
} error_info;
struct {
int exit_code;
char result[50];
} finished_info;
} StateData;
typedef struct {
ProcessState current_state;
StateData data;
} Process;
void print_process_status(Process proc) {
printf("Process State: ");
switch (proc.current_state) {
case STATE_IDLE:
printf("IDLE - Task %d: %s\n",
proc.data.idle_info.task_id,
proc.data.idle_info.description);
break;
case STATE_RUNNING:
printf("RUNNING - Progress: %.1f%%, Time: %d seconds\n",
proc.data.running_info.progress,
proc.data.running_info.elapsed_time);
break;
case STATE_PAUSED:
printf("PAUSED - Reason: %d, Resume at: %d\n",
proc.data.paused_info.pause_reason,
proc.data.paused_info.resume_time);
break;
case STATE_ERROR:
printf("ERROR - Code: %d, Message: %s\n",
proc.data.error_info.error_code,
proc.data.error_info.error_message);
break;
case STATE_FINISHED:
printf("FINISHED - Exit code: %d, Result: %s\n",
proc.data.finished_info.exit_code,
proc.data.finished_info.result);
break;
}
}
int main() {
Process proc;
// Initialize as idle
proc.current_state = STATE_IDLE;
proc.data.idle_info.task_id = 1001;
strcpy(proc.data.idle_info.description, "Data Processing");
print_process_status(proc);
// Change to running
proc.current_state = STATE_RUNNING;
proc.data.running_info.progress = 45.5f;
proc.data.running_info.elapsed_time = 120;
print_process_status(proc);
// Change to finished
proc.current_state = STATE_FINISHED;
proc.data.finished_info.exit_code = 0;
strcpy(proc.data.finished_info.result, "Success");
print_process_status(proc);
return 0;
}
Memory Considerations
Union Size
#include <stdio.h>
union Test {
char c; // 1 byte
int i; // 4 bytes
double d; // 8 bytes
};
int main() {
printf("Size of union: %zu bytes\n", sizeof(union Test));
printf("Size of char: %zu bytes\n", sizeof(char));
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of double: %zu bytes\n", sizeof(double));
return 0;
}
Structure vs Union Memory Usage
#include <stdio.h>
struct StructExample {
char c;
int i;
double d;
};
union UnionExample {
char c;
int i;
double d;
};
int main() {
printf("Structure size: %zu bytes\n", sizeof(struct StructExample));
printf("Union size: %zu bytes\n", sizeof(union UnionExample));
return 0;
}
Best Practices
Enum Best Practices
- Use meaningful names for enum constants
- Consider using typedef for cleaner code
- Group related constants together
- Document the meaning of each constant
- Avoid magic numbers by using enums
Union Best Practices
- Always track which member is currently valid
- Use unions with discriminant (enum) to track type
- Be careful with memory sharing - only one member is valid at a time
- Use unions for memory optimization, not for type punning
- Document union usage clearly
Common Pitfalls
1. Accessing Wrong Union Member
union Data {
int i;
float f;
};
union Data data;
data.i = 42;
// printf("%f\n", data.f); // Wrong! Contains integer data
2. Forgetting Union Size
union BigUnion {
char small; // 1 byte
long long big; // 8 bytes
};
// Union will be 8 bytes, not 1 byte!
3. Enum Value Collisions
enum Colors { RED = 1, GREEN = 2, BLUE = 3 };
enum Shapes { CIRCLE = 1, SQUARE = 2, TRIANGLE = 3 };
// RED == CIRCLE, GREEN == SQUARE, etc. - can cause confusion
Conclusion
Unions and enumerations are powerful C features that improve code organization and memory efficiency. Unions allow memory sharing between different data types, while enumerations provide meaningful names for constants. Understanding when and how to use these features will make your C programs more efficient and maintainable! 🚀