Tamil Technicians – C Programming Course · Lesson 13
Unions & Enums in C – Where and Why to Use Them?
As you go deeper into C programming, you will sometimes need more expressive and memory-efficient ways to represent data. So far, you have seen:
- Basic types:
int,float,char, etc. - Arrays: 1D and 2D for lists and tables.
struct: to group different fields into one record.
Two additional tools in C are:
union and enum.
They are less used in beginner programs, but very important in real systems,
especially in embedded programming, device drivers and protocol parsers.
In this lesson you will learn:
- What a union is and how it shares memory between fields.
- Real examples where unions save memory or simplify “variant” data.
- What an enum is and why it’s better than plain integer codes.
- How to use enums with
switchfor readable state machines. - Best practices and common mistakes to avoid.
Quick Overview – What Are Unions and Enums?
Union (Shared Memory)
A union is like a struct, but
all members share the same memory location.
At any point, you are expected to use only one active field.
This is useful when a value can be of different types at different times, and you want to save memory.
Enum (Named Integer Values)
An enum (enumeration) is a way to give names to integer constants.
This makes code more readable and self-explanatory than using
raw numbers like 0, 1, 2 for modes or states.
Use unions when you need different representations of the same data in the same memory space. Use enums when you need meaningful, named states or categories instead of “magic numbers”.
Enums in C – Named Integer Constants
An enum lets you group related integer constants together under one type name.
Syntax:
enum enum_name {
CONSTANT1,
CONSTANT2,
CONSTANT3
};
Example – device modes:
enum DeviceMode {
MODE_OFF, // 0
MODE_IDLE, // 1
MODE_RUN, // 2
MODE_ERROR // 3
};
Under the hood, MODE_OFF is just an int with value 0,
MODE_IDLE is 1, and so on (by default).
enum DeviceMode {
MODE_OFF = 0,
MODE_IDLE = 10,
MODE_RUN = 20,
MODE_ERROR = 99
};
Using Enums – Why They Are Better Than Plain Integers
Without enums, you might write:
#define MODE_OFF 0
#define MODE_IDLE 1
#define MODE_RUN 2
#define MODE_ERROR 3
int mode = 2; // what is 2?
If someone reads mode = 2;, they have to search where 2 is defined.
With enums:
enum DeviceMode mode;
mode = MODE_RUN; // much clearer
Example: using enum with switch:
#include <stdio.h>
enum DeviceMode {
MODE_OFF,
MODE_IDLE,
MODE_RUN,
MODE_ERROR
};
int main() {
enum DeviceMode mode = MODE_RUN;
switch (mode) {
case MODE_OFF:
printf("Device is OFF\\n");
break;
case MODE_IDLE:
printf("Device is IDLE\\n");
break;
case MODE_RUN:
printf("Device is RUNNING\\n");
break;
case MODE_ERROR:
printf("Device ERROR\\n");
break;
}
return 0;
}
- Code is more readable (
MODE_RUNvs2). - Less chance of typo with random numbers.
- Easy to add new states later.
- Great with
switchfor state machines and menus.
Practical Enum Examples for Technicians
Example 1 – Error Codes
enum ErrorCode {
ERR_OK = 0,
ERR_SENSOR_FAIL = 1,
ERR_OVER_VOLTAGE = 2,
ERR_OVER_CURRENT = 3,
ERR_COMMUNICATION = 4
};
Using this, you can return meaningful error codes from functions:
enum ErrorCode readSensor() {
// ...
return ERR_SENSOR_FAIL;
}
Example 2 – Menu Choices
enum MenuChoice {
MENU_NONE = 0,
MENU_START = 1,
MENU_STOP = 2,
MENU_STATUS = 3,
MENU_EXIT = 9
};
Now your switch on user input will be much clearer
when you use constants like MENU_EXIT instead of just 9.
Unions in C – Shared Memory for Different Views
A union is similar to a struct, but with one key difference:
In a struct, each member has its own memory. In a union, all members share the same memory location. The size of a union is equal to the size of its largest member.
Basic Syntax
union Data {
int i;
float f;
char c;
};
Here, Data can hold either an int, or a float,
or a char – but not all three meaningfully at the same time.
union Data d;
d.i = 42; // store int
// later
d.f = 3.14f; // now interpreted as float, overwrites previous int
The memory is shared, so the last write decides which type of data is stored.
Union vs Struct – Memory Diagram
| Feature | struct | union |
|---|---|---|
| Memory layout | Each member has its own space; total size ≥ sum of members. | All members share the same space; size = largest member. |
| Usage | Store many related values at once. | Store one of several possible representations at a time. |
| Example | Student record: name, age, marks, etc. | Packet payload that can be in different formats. |
Struct Example
struct Point {
int x;
int y;
};
This always stores both x and y.
Union Example
union Number {
int i;
float f;
};
Here the same memory is viewed as either an int or a float.
You choose which interpretation is valid at any time.
Where and Why to Use Unions?
Unions are most useful in these situations:
- Memory-constrained systems – Embedded devices, microcontrollers, where RAM is very limited.
- Variant data – When a value can be in different formats at different times (like multiple sensor formats, different packet types).
- Reinterpreting data – Viewing the same bytes as different types (for example, breaking a 32-bit integer into 4 bytes).
Example 1 – Sensor Value with Multiple Units
Suppose your system can represent a measurement as integer milli-units or as float units, but at runtime you only need one representation.
union SensorValue {
int milli; // value in milli-units
float real; // value as float
};
enum ValueType {
VAL_MILLI,
VAL_REAL
};
struct Reading {
enum ValueType type;
union SensorValue value;
};
Here, Reading is a classic example of a
tagged union: the type field (enum) tells you
how to interpret the union value.
struct Reading r;
r.type = VAL_MILLI;
r.value.milli = 12345; // 12.345 units
// Later
r.type = VAL_REAL;
r.value.real = 12.345f;
Example 2 – Breaking a 32-bit Value into Bytes
In embedded or communication code, you might want to send a 32-bit integer as 4 bytes over a serial line. A union can make this convenient:
#include <stdio.h>
#include <stdint.h>
union U32Bytes {
uint32_t value;
unsigned char bytes[4];
};
int main() {
union U32Bytes u;
u.value = 0x12345678;
printf("Bytes: %02X %02X %02X %02X\\n",
u.bytes[0], u.bytes[1], u.bytes[2], u.bytes[3]);
return 0;
}
Here the same memory is accessed as a 32-bit integer and as a 4-byte array. Note that endianness (byte order) depends on the CPU, so this is platform-specific – but this pattern is widely used in low-level code.
Caution: Dangers & Limitations of Unions
- Only one active member: You must know which member is currently valid. That’s why we often use an enum tag.
- Strict aliasing & portability: Reinterpreting memory as a different type can be non-portable and must be done carefully, especially with floating-point types.
- Alignment: The union must be aligned for all member types; compilers handle this, but the size may be bigger than the largest member due to padding.
u.f and then read from u.i (with different meanings),
the result is often implementation-defined or undefined behaviour.
Only use union type-punning when you understand the platform and compiler rules.
Where & Why to Use Enums vs Unions (Summary)
Use enums when…
- You have a small set of states (modes, errors, menu options).
- You currently use
#defineor magic numbers for codes. - You want clearer, self-documenting code.
- You write
switch-based state machines.
Use unions when…
- You need one of several data formats, but only one at a time.
- You work in memory-constrained systems (embedded, microcontroller).
- You implement protocols or packets with variant payloads.
- You need to reinterpret the same bytes as different views (with care).
Practice Tasks – Unions & Enums
-
Enum-based Menu:
Create an
enumfor menu choices and write aswitch-based menu program that uses these enum constants instead of numbers. -
Error Handling:
Define an
enum ErrorCodeand use it as the return type of functions. Print meaningful messages based on the enum values. -
Tagged Union for Measurements:
Create a
structthat contains anenumtype and aunionof int/float, then write a function to print the value based on the type. -
Packet Union:
Design a
unionrepresenting a packet payload that can either be: (a) threeuint8_tbytes, or (b) oneuint16_t+ oneuint8_t. Use the union to fill and print the bytes. -
Enum + Array Table:
Define an
enumfor days of the week and an array that stores work hours for each day. Print a weekly report using the enum names.
Suggested Featured Image Prompt
Use this in your AI image generator for the Tamil Technicians blog thumbnail:
“Flat modern 16:9 illustration on a light background. On the left, a memory block is drawn as a single horizontal bar labeled ‘union memory’, with overlapping labels `int`, `float`, and `bytes[4]` showing that different views share the same space. Above it, small C code text shows `union Data { int i; float f; unsigned char bytes[4]; };`. On the right, a vertical list of colored tags represents an enum with items like `MODE_OFF`, `MODE_IDLE`, `MODE_RUN`, each with small icons (power button, pause, play). At the bottom, a South Indian / Tamil technician character is pointing to the union memory diagram with one hand and to the enum list with the other, with a small caption ‘Where & Why to Use Them?’. At the top, bold title text: ‘Unions & Enums in C’ and smaller subtitle: ‘Where and Why to Use Them? | Tamil Technicians’. Clean vector style, minimal colors, educational, high quality.”
FAQ: Unions & Enums in C
1. Are enums just integers in C?
Yes. In C, enums are essentially integers under the hood. Each enum constant has an integer value, usually starting from 0 and increasing by 1 unless you specify values. However, using enums improves readability and helps group related constants together.
2. Can I print an enum directly with printf?
You can print an enum using %d because it is stored as an integer:
printf("%d", mode);. To print the name (like “MODE_RUN”), you must write a helper
function that maps each enum value to a string.
3. Can a union store all members at the same time?
Physically, all members occupy the same memory region, but logically only one member should be considered valid at a time. If you write to one member and then read from another with a different type, the result is usually undefined, unless you are doing careful low-level type punning and know the rules.
4. Are unions necessary for normal application programming?
For many high-level applications, you can write perfectly fine C without unions. But in embedded systems, protocol parsing, and memory-critical code, unions are very helpful. Even if you don’t use them daily, understanding unions will make you a more complete C programmer.


