One space, several possible types
A union stores any one of its members at a time in the same memory. It saves space but forgets which member is valid, so you must remember for it.
After this lesson
You should be able to
- Declare a union and explain how it differs from a structure.
- Explain why reading a member you did not write is unsafe.
- Pair a union with a tag field to make it safe.
Members share the space
In a structure every member has its own memory, so the size is roughly the sum of the members. In a union all members start at the same address, so the size is that of the largest member and only one member is meaningful at a time.
Writing one member overwrites the others. There is no error and no warning — the bytes are simply reinterpreted.
union Value {
int i;
double d;
char text[8];
};
union Value v;
v.i = 65;
printf("%d\n", v.i); /* 65 — fine */
v.d = 3.5; /* overwrites v.i */
printf("%d\n", v.i); /* garbage, not 65 */The union does not remember
Nothing inside a union records which member was last written. Reading a member you did not write reinterprets whatever bytes are there, which is undefined behaviour in general and produces meaningless values in practice.
So a bare union is rarely used. The standard fix is to keep a tag alongside it that says which member is currently valid.
A tagged union is the safe form
Wrap the union in a structure with an enum tag. The tag says which member is live, and every read goes through a switch on the tag. Now the compiler cannot check it for you, but your code can.
This is how a value that may be a number or a piece of text is represented in C. Set the tag and the member together, always, and never one without the other.
typedef enum {IS_INT, IS_TEXT} Kind;
typedef struct {
Kind kind;
union {
int number;
char text[20];
} as;
} Field;
void print_field(const Field *f)
{
if (f->kind == IS_INT)
printf("%d\n", f->as.number);
else
printf("%s\n", f->as.text);
}Try it yourself
Model a form field that holds either a roll number or a name, and print three such fields correctly.
Need a hint?
You need the enum tag, otherwise printing is guesswork.
Check the worked solution
Each initialiser sets the tag and the matching member together, which is the discipline that makes this safe. Note the memory saving is real but small here; the reason to use a union is that the value genuinely is one thing or the other, not that you are short of bytes.
#include <stdio.h>
#include <string.h>
typedef enum {IS_INT, IS_TEXT} Kind;
typedef struct {
Kind kind;
union {
int number;
char text[20];
} as;
} Field;
void print_field(const Field *f)
{
if (f->kind == IS_INT)
printf("Number: %d\n", f->as.number);
else
printf("Text: %s\n", f->as.text);
}
int main(void)
{
Field fields[3];
fields[0].kind = IS_INT;
fields[0].as.number = 101;
fields[1].kind = IS_TEXT;
strcpy(fields[1].as.text, "Kavya");
fields[2].kind = IS_INT;
fields[2].as.number = 87;
for (int i = 0; i < 3; i++)
print_field(&fields[i]);
return 0;
}Quick check
How much memory does a union with an int and a double occupy?
Why this lesson exists
Syllabus mapping
Union Types
Maps to course outcome CO5.