C/C++ Arena

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

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.

Previous: typedef