Lesson 13 · 13 of 20
Unions and bit fields · C
Store values of different types in a tagged union and pack a date into bits with C bit fields, checking their size with sizeof.

In a union every member starts at the same address and shares the same bytes: only one makes sense at a time. That is why it takes the size of its biggest member; here sizeof config[0].value is 8, the size of a double or a pointer.
Because the union does not remember which member was written, it comes with a tag: in Setting, the kind field (an enum) says whether the value is INT, REAL or TEXT. This is a tagged union, the classic C way to hold a value that can be one of several types, such as the settings in a configuration file.
When initialising, you pick the member by name: {.i = 8080}, {.d = 2.5}, {.s = "es"}. show checks the tag with a switch and reads only the matching member.
Bit fields state how many bits each field takes: day : 5 goes up to 31, month : 4 up to 15 and year : 12 up to 4095. All three fit in one unsigned int, and sizeof release is 4 bytes instead of 12. The exact bit order depends on the compiler, so they are not for reading someone else's binary format.
The last union reads the bytes of a float as a 32-bit integer: 1.0f is stored as 0x3F800000, as IEEE 754 specifies. In C, reading a member other than the one last written is allowed and reinterprets the bytes.
Code · main.c
#include <stdint.h>#include <stdio.h>// A tagged union: the enum says which member of the union is in use.typedef enum { INT, REAL, TEXT } Kind;typedef struct { const char *name; Kind kind; union { int i; double d; const char *s; } value; // all share the same bytes} Setting;// Bit fields: a date packed into 21 bits of one unsigned int.typedef struct { unsigned day : 5, month : 4, year : 12; } Date;void show(const Setting *s) { switch (s->kind) { case INT: printf("%-9s = %d\n", s->name, s->value.i); break; case REAL: printf("%-9s = %.2f\n", s->name, s->value.d); break; case TEXT: printf("%-9s = \"%s\"\n", s->name, s->value.s); break; }}int main(void) { Setting config[] = {{"port", INT, {.i = 8080}}, {"timeout", REAL, {.d = 2.5}}, {"language", TEXT, {.s = "es"}}}; for (int k = 0; k < 3; k++) show(&config[k]); printf("sizeof union: %zu (its biggest member)\n", sizeof config[0].value); Date release = {.day = 14, .month = 3, .year = 2026}; printf("Date %02u/%02u/%u in %zu bytes\n", release.day, release.month, release.year, sizeof release); union { float f; uint32_t bits; } pun = {.f = 1.0f}; // read the bytes of a float printf("1.0f is stored as 0x%08X\n", (unsigned)pun.bits); return 0;}Output · cc main.c -o main && ./main
port = 8080 timeout = 2.50 language = "es" sizeof union: 8 (its biggest member) Date 14/03/2026 in 4 bytes 1.0f is stored as 0x3F800000
Key points
- A union's members share the same bytes.
- A union is as big as its biggest member.
- A tag (
enum) says which member is valid. field : ntakes onlynbits.
Exercise
Add a FLAG kind with an int on member in the union, create a dark_mode setting and make show print on or off.