Introduction
In the previous lesson, you learned how to model related data using structures. In this lesson, you will explore unions—a closely related but very different feature of C that lets multiple variables share the same memory location. You will see how unions are declared and used, how they differ from structures in terms of memory layout, and where they are useful in real-world programs.
By the end, you should be comfortable declaring unions, accessing their members, and understanding how data type sizes affect their behavior.
What Is a Union and Why Use It?
A union is a user-defined type that allows different members to share the same memory. At any given time, a union can reliably hold only one of its members.
You might use a union when:
- You want to store one of several possible data types in the same memory region.
- You are working with memory-constrained systems (e.g., embedded devices) and want to optimize memory.
- You need to interpret the same bytes in different ways (e.g., protocol parsing, bit-level operations).
Conceptually:
- A struct = "all of these fields at once"
- A union = "exactly one of these fields at a time, sharing the same memory"
Declaring and Using a Union
Basic Union Syntax
The syntax of a union is similar to a struct:
union UnionName {
type1 member1;
type2 member2;
// ... more members
};
Simple Example: Number as Int or Float
#include <stdio.h>
union Number {
int i;
float f;
};
int main(void) {
union Number n; // declare a union variable
n.i = 42; // store an int
printf("As int: %d\n", n.i);
n.f = 3.14f; // store a float (overwrites same memory)
printf("As float: %.2f\n", n.f);
// After writing to n.f, n.i now refers to the same bytes
printf("Interpreted as int now: %d\n", n.i);
return 0;
}
// Output
As int: 42
As float: 3.14
Interpreted as int now: (some implementation-dependent value)
Explanation:
union Numberhas two members:int iandfloat f.n.i = 42;stores 42 in the shared memory.n.f = 3.14f;then stores a float over the same bytes, so the original integer is no longer valid.- Reading
n.iafter writingn.finterprets the float’s bytes as an int, producing an unpredictable, implementation-dependent integer.
This illustrates a crucial rule: only the most recently assigned member of a union should be read.
Memory Usage: Structures vs Unions
You already know that structures allocate memory for all their members. Unions are different.
- Struct size ≈ sum of member sizes (plus padding for alignment).
- Union size = size of its largest member (plus possible padding).
Comparing Memory Layout
#include <stdio.h>
struct SData {
int i;
float f;
char c;
};
union UData {
int i;
float f;
char c;
};
int main(void) {
printf("Size of struct SData: %zu bytes\n", sizeof(struct SData));
printf("Size of union UData: %zu bytes\n", sizeof(union UData));
return 0;
}
// Possible Output (depends on system)
Size of struct SData: 12 bytes
Size of union UData: 4 bytes
Explanation:
- The struct holds all three:
int,float, andchar, so its size is at leastsizeof(int) + sizeof(float) + sizeof(char)(plus padding). - The union’s size is the maximum of its members: here likely
4bytes (size ofintorfloaton many systems). - This compactness is the main reason to use unions when you know only one member will be active at a time.
Real-World Style Example: Sensor Reading
Imagine a sensor that can return either an integer status code or a floating-point measured value, but never both at the same time. A union models this nicely.
#include <stdio.h>
enum ReadingType {
READING_STATUS,
READING_VALUE
};
union SensorData {
int status; // e.g., error code
float value; // e.g., temperature
};
struct SensorReading {
enum ReadingType type; // tells us which union member is valid
union SensorData data; // union holding the actual data
};
void print_reading(struct SensorReading r) {
if (r.type == READING_STATUS) {
printf("Status code: %d\n", r.data.status);
} else if (r.type == READING_VALUE) {
printf("Sensor value: %.2f\n", r.data.value);
}
}
int main(void) {
struct SensorReading r1;
r1.type = READING_STATUS;
r1.data.status = -1; // error code
struct SensorReading r2;
r2.type = READING_VALUE;
r2.data.value = 23.75f; // temperature
print_reading(r1);
print_reading(r2);
return 0;
}
// Output
Status code: -1
Sensor value: 23.75
Explanation:
union SensorDatacan hold either anint statusor afloat value.struct SensorReadingpairs the union with anenum ReadingTypetag. This pattern is often called a tagged union or discriminated union.- The tag (
type) tells us which union member is currently valid, so we avoid reading the wrong member. - Memory-wise, the union still uses only as much space as its largest member, while the struct adds just the tag overhead.
This is a practical, safe way to use unions in larger programs.
Impact of Data Types and Sizes in Unions
Since all members share the same memory:
-
Largest member dominates size
If a union containschar,int, anddouble:- The union’s size will be at least
sizeof(double).
- The union’s size will be at least
-
Alignment and padding matter
The union will usually be aligned to suit its most strictly aligned member (e.g.,double). -
Interpreting bytes incorrectly is dangerous
Assign to one member, then reading another is only meaningful when you specifically intend to reinterpret the raw bytes (e.g., bit patterns, low-level I/O). Otherwise, it leads to undefined or non-portable behavior. -
Portability concerns
Bit patterns for types likefloatandintdepend on:- Endianness (byte order)
- Floating-point representation
- Compiler-specific padding/alignment
Therefore, unions are powerful but must be used with a clear understanding of the data sizes and representation.
Summary / Key Takeaways
In this lesson on Introduction to Unions, you have learned:
- Unions share memory among members, allowing different types to occupy the same storage location, with only one member valid at a time.
- Union syntax closely resembles struct syntax, but their semantics differ because all members overlap in memory.
- Structs allocate space for all members, while unions allocate space only for the largest member, making unions more memory-efficient when alternatives are mutually exclusive.
- Reading a union member different from the last written one is unsafe unless you intentionally reinterpret the underlying bytes for low-level purposes.
- Tagged unions (union + enum + struct) provide a safe pattern, where an explicit tag records which union member currently holds valid data.
- Data type sizes and alignment directly influence union size and behavior, since the largest and most strictly aligned member determines the memory footprint and layout.
- Unions fit real-world use cases like sensor readings, protocol parsing, and compact configuration storage, where only one of several related representations is active at once.
- Careful design is required for portable code using unions, especially when mixing types with different sizes or representations such as integers and floating-point values.