Using a union to "save space", that is using it to create a variant, is usually considered bad practice (for example banned by MISRA-C) and nothing I would recommend. The presence of a variant type is almost always a sign of bad program design.
The most common purpose of unions is otherwise to use them for type punning, which means that you can write to one union member and then read that data through another member. This is well-defined behavior in C and quite useful when doing hardware-related programming.
For example you can do something like this:
typedef union
{
uint32_t u32;
uint8_t u8 [4];
} my32_t;
my32_t my32;
my32.u32 = 1;
if(my32.u8[0] == 1)
{
puts("little endian");
}
else
{
puts("big endian");
}
Unions can also be used to dodge the strict aliasing rules. Code like this would invoke undefined behavior:
uint32_t u32 = 1;
uint16_t u16 = *(uint16_t*)&u32; // bad, strict aliasing violation
To avoid this bug, the code could be rewritten as:
typedef union
{
uint32_t u32;
uint16_t u16;
} something_t;
something_t x;
x.u32 = 1;
uint16_t u16 = x.u16;
This is well-defined behavior as per the exception from strict aliasing listed in 6.5/7:
- an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a
subaggregate or contained union)