A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
A union is a struct where all members occupy the same memory. The size of the union is the size of its largest member. Writing to one member overwrites the others. Only one member is meaningful at any given time. Unions are used for type punning (interpreting the same bytes as different types), for variant records (a value that can be an int or a float or a pointer), and for memory-mapped hardware registers where the same address has multiple interpretations.
#include <stdio.h>union Data {int i;float f;char str[8];};int main(void) {union Data d;printf("sizeof(union Data): %zu\n", sizeof(d));d.i = 42;printf("As int: %d\n", d.i);d.f = 3.14f;printf("As float: %.2f\n", d.f);printf("As int (overwritten): %d\n", d.i);return 0;}
The union is 8 bytes: the size of the largest member (char[8]). Writing d.f overwrote the bytes that d.i was using. Reading d.i after writing d.f gives the bit pattern of 3.14f interpreted as an integer: 1078523331. The bytes are the same. The interpretation changed. This is type punning, and it is the primary legitimate use of unions.
#include <stdio.h>#include <string.h>typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } ValueType;typedef struct {ValueType type;union {int i;float f;char str[32];} data;} Value;void print_value(const Value *v) {switch (v->type) {case TYPE_INT:printf("int: %d\n", v->data.i);break;case TYPE_FLOAT:printf("float: %.2f\n", v->data.f);break;case TYPE_STRING:printf("string: \"%s\"\n", v->data.str);break;}}int main(void) {Value v1 = {TYPE_INT, {.i = 42}};Value v2 = {TYPE_FLOAT, {.f = 3.14f}};Value v3 = {TYPE_STRING};strncpy(v3.data.str, "hello", sizeof(v3.data.str) - 1);print_value(&v1);print_value(&v2);print_value(&v3);return 0;}
The enum tag tells you which union member is active. The switch in print_value reads only the correct member. This is the tagged union pattern, also called a variant record or a discriminated union. It is how C implements sum types: a value that can be one of several types, with a tag indicating which one. JSON parsers, bytecode interpreters, and protocol handlers in C all use this pattern.
Unions are a memory-efficient way to represent "one of several types" when the types are known at compile time. The tagged union pattern adds the runtime discrimination. Together, they are C's answer to algebraic data types. Next: dynamically allocating an array of structs, combining heap management with composite types.
Finish the lesson once you have worked through the material. This awards ★ 30 XP.