Step 7 of 7
Unions and tagged unions
A struct holds all of its members at once, each in its own memory. A union looks the same but holds one member at a time: every member starts at the same address and shares the same bytes. A union is as big as its largest member (possibly rounded up for alignment), not the sum of them.
On its own, a union doesn't remember which member you last stored, so real code pairs it with an enum that says which one is in use. That combination is called a tagged union, and it's C's way of saying "a value that can be one of several types":
#include <stdio.h>
enum Kind { NUMBER, TEXT };
struct Value {
enum Kind kind; /* which union member is in use */
union {
double number;
char text[16];
} as;
};
void print_value(struct Value v) {
if (v.kind == NUMBER) {
printf("number %.2f\n", v.as.number);
} else {
printf("text \"%s\"\n", v.as.text);
}
}
int main(void) {
struct Value a = {NUMBER, {.number = 3.5}};
struct Value b = {.kind = TEXT, .as.text = "hello"};
print_value(a);
print_value(b);
printf("union: %zu bytes; members: %zu and %zu\n", sizeof a.as, sizeof a.as.number, sizeof a.as.text);
return 0;
}
number 3.50
text "hello"
union: 16 bytes; members: 8 and 16
How it works
union { double number; char text[16]; } as;declares a member calledaswhose type is a union.v.as.numberandv.as.textare two views of the same 16 bytes.- The union is 16 bytes, the size of its biggest member, even though it can hold an 8-byte
double. A struct with both members would need 24. {.number = 3.5}and.as.text = "hello"use designated initializers to say which member to fill in.- Every reader checks
kindfirst and only touches the member that matches. Keeping the tag and the data in step is the programmer's job: the compiler won't stop you from readingas.numberwhenkindsaysTEXT.
Reading the wrong member
If you store 3.5 into as.number and then read as.text, C reinterprets the same bytes as characters. That's allowed in C, but what you get depends on how the machine stores a double, so it's rarely what you want. (In C++, reading a union member other than the one last stored is undefined behavior.) Use unions for "one of several things", with a tag, and not as a trick for converting between types.
Where they're used
Tagged unions appear wherever data comes in several shapes: the values in an interpreter (a number, a string or a list), messages in a network protocol (each kind of message has a different payload), events in a game or GUI (a key press, a mouse click, a resize). They save memory when you have many such values, and they keep "one of these" explicit in the type. C++'s std::variant, later in the course, is a tagged union that tracks the tag for you.
Your turn: a struct Shape is a circle, a rectangle or a triangle, with a union holding each kind's measurements. Write area(s) (a triangle's area is base × height / 2) and make_circle(r), which returns a circle shape with radius r.