Sometimes, you need a data structure that can represent different data types depending on the situation. For instance, if you use a single structure to represent the quantity of fruit, it may need to be an integer (like 6 apples) at times and a float (like 1.5 kilograms of strawberries) at others.

In C, you can use a union to create a flexible data structure. A union can contain various members that share the same memory space, meaning only one member can hold a meaningful value at any given time. Assigning a new value to one member will overwrite the previous one, which is a key advantage for memory savings.

Here’s an example:

1
2
3
4
5
union quantity {
short count;
float weight;
float volume;
};

In this example, the union quantity defines three members, but only one can hold a value at a time. The last assigned member will be the one that holds a meaningful value.

To use it, declare a variable of this type:

1
2
union quantity q;
q.count = 4;

You can also assign values using different syntax:

1
2
union quantity q = {.count=4};
union quantity q = {4};

In the example above, if the last writing style does not specify a member name, it will be assigned to the first member.

After running this code, q.count holds the value, while the other members do not. Accessing them can lead to undefined behavior until assigned a value.

1
2
printf("count is %i\n", q.count); // count is 4
printf("weight is %f\n", q.weight); // undefined behavior

To access the weight, you must assign it first:

1
2
q.weight = 0.5;
printf("weight is %f\n", q.weight); // weight is 0.5

Once you assign a value to another member, the original member may no longer hold a meaningful value. The usage of unions is similar to structures otherwise.

Unions also support the pointer operator ->:

1
2
3
4
5
union quantity q;
q.count = 4;

union quantity* ptr = &q;
printf("%d\n", ptr->count); // 4

Here, ptr points to q, allowing access to its members directly.

When using unions with pointers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
union foo {
int a;
float b;
} x;

int* foo_int_p = (int *)&x;
float* foo_float_p = (float *)&x;

x.a = 12;
printf("%d\n", x.a); // 12
printf("%d\n", *foo_int_p); // 12

x.b = 3.141592;
printf("%f\n", x.b); // 3.141592
printf("%f\n", *foo_float_p); // 3.141592

The type of the pointer is determined by the current member that is assigned a value.

You can also create type aliases for unions using typedef:

1
2
3
4
5
typedef union {
short count;
float weight;
float volume;
} quantity;

This way, quantity becomes an alias for the union type.

The primary benefit of using unions is memory efficiency, as they allow the reuse of memory for different data types, occupying space equal to the largest member.