DMarlon Studio

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.

C · Unions and bit fields in DMarlon Studio: the example's code and its output in the terminal

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 : n takes only n bits.

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.