Proven C Book한국어 GitHub

48 Unions and representation

What to know first

chapter 46, Structs · the layout of a struct
chapter 5, Words and addresses · seeing a representation as bytes
chapter 13, Compiler optimisation · strict aliasing

Looking back

Chapter 13 said the old technique of “reading a float’s bits through uint32′s eye” violates strict aliasing, and that the correct methods are memcpy or a union. Then what exactly is a union, that it stands in that place?

A. A type whose several members share the same memory. If a struct lays members side by side (chapter 46), a union lays them overlapping — its size fits the largest member, and at any moment only one thing is really held. Chapter 5′s perspective, “seeing the same bits through this eye and through that”, made into syntax.

The need for this chapter, and its context

Part 8 closes with unions not because it is the last shape of data, but because the separation of representation from abstraction — this book’s refrain — sounds here for the last time at full volume. It is also where the endianness demonstration reserved back in chapter 5 finally runs as real code. A fitting place just before part 9′s “deep corners”.

By the end of this chapter

The device for seeing the same memory through a different eye — the union. And this is a chapter of representation too: we run the endianness demonstration booked in chapter 5 and confirm with our own eyes the hidden gaps (padding) in a struct. It is where this book’s refrain, the separation of representation from abstraction, rings out loudest for the last time.

The questions this chapter answers

  1. When, then, is a union the standard thing to use?
  2. So does every bit pattern become a value?

(In chapter 26′s families a union was a derived type but not an aggregate — because only one member is alive at a time. This chapter shows that reason in the flesh.)

48.1 The union — laying things over one another

The syntax is a twin of the struct’s. Change struct to union:

union bits32 {
    uint32_t as_int;
    float    as_float;
};

Member access is the same (u.as_int). The only difference is the layout of memory — the two members share the same four bytes, so write to as_float and read as_int and you see the same bits under a different interpretation. The C standard permits this “read through a member other than the one written” (type punning) for unions in particular — unlike chapter 13′s pointer-cast approach, it is inside the contract, which is the decisive difference (though the caution remains that the value read may not be a valid value of that type).

48.2 Representation with our own eyes — endianness and padding

Chapter 5 booked “actually doing this check in C is this chapter’s demonstration.” Now we pay. Here, instead of a union, we use the most portable method — the eye of bytes (unsigned char) learned in chapter 37, and memcpy.

examples-en/ch48/endian.c

#include <stdio.h>
#include <stdint.h>
#include <string.h>

int main(void)
{
    uint32_t value = 0x12345678u;
    unsigned char bytes[4];

    memcpy(bytes, &value, sizeof value);   /* look into the representation byte by byte */

    printf("value: 0x%08X\n", value);
    printf("memory order: %02X %02X %02X %02X\n",
           bytes[0], bytes[1], bytes[2], bytes[3]);
    printf("this machine is %s-endian\n",
           bytes[0] == 0x78 ? "little" : "big");

    struct padded {
        char  tag;      /* 1 byte */
        int   count;    /* 4 bytes — alignment opens a gap in front of it */
    };
    printf("1 + 4 = 5, but sizeof = %zu\n", sizeof(struct padded));
    return 0;
}

Output

value: 0x12345678
memory order: 78 56 34 12
this machine is little-endian
1 + 4 = 5, but sizeof = 8

The first part is exactly chapter 5′s picture. 0x12345678 sits in memory in the order 78 56 34 12 — meaning this book’s verification machine is little-endian, and chapter 5′s diagram is confirmed in the flesh. (Run this example on a big-endian machine and 12 34 56 78 is printed and the verdict sentence changes — the code stays the same.)

The second part is the struct’s hidden circumstances. A struct holding one char (1 byte) and one int (4 bytes) has size 8, not 5 — because chapter 6′s alignment rule inserted three bytes of padding after the char. The int member must start at a multiple of four for the machine to grab it in one handful (chapter 6). So one practical habit follows — lay the large members first and the gaps shrink. In code handling millions of structs this one layout decision governs memory and cache efficiency (chapter 11). The layout rules, and the ways to remove gaps or force alignment (pack, alignas), were treated in detail in chapter 47 — the purpose here is to confirm with our own eyes that the gap really exists.

In practice. The secret spilled by a gap — padding information leaks in kernels

Padding looks harmless, being empty space nobody uses, but through the eye of security it is uninitialised memory. When an operating system kernel copies a struct whole to a user program, those gaps cross over too — and although every member was filled in, the gaps contain the remains of other data that happened to be there. An attacker can gather these crumbs to glimpse the contents or address layout of kernel memory, and that becomes the foothold for the next attack. Major kernels including Linux have fixed dozens of information-leak vulnerabilities of this class, and today’s response is simple — a struct handed to userspace is wiped to zero whole before its members are filled. It is the moment chapter 23′s rule of “initialise at the point of declaration” extends even to invisible blanks.

A common misconception. “You can just write a struct to a file or send it over a network as it is”

A tempting thought, and one much attempted — storing a struct’s bytes whole makes the code short. But the two facts this chapter has just shown block it: byte order differs by machine (endianness), and the size and position of the gaps differ by compiler and platform (padding). A file written on one machine breaks on another — chapter 5′s NUXI incident reproduced in the world of file formats. The right answer is serialisation: writing explicit code that writes and reads members one at a time, in an agreed size and byte order (network byte order — chapter 5). Representation is the machine’s business and files and communication are a world of agreements — the bridge between the two worlds must be laid by hand.

Q. When, then, is a union the standard thing to use?

A. In two places. First, the looking into representation (type punning) just seen — low-level code inspecting floating-point bits or viewing a hardware register through several eyes. Second, and more common, the tagged union: putting into a struct both a union and a mark (a tag) saying “which member is valid now”, to represent alternative data such as “this value is an integer, or a real number, or a string.” It is the basic tool of interpreters’ value representations and configuration-file parsers — and chapter 6′s tagged pointer was the same idea at the bit level. Modern languages’ enumerations (Rust’s enum, Swift’s associated values) lifted this pattern to the level of the language.

48.3 The active member and type punning — the same bits through another eye

The contract of a union comes down to one term: the active member — the one whose value was written last. So what happens if you read a member that is not active? The answer to that question is where C and C++ part.

examples-en/ch48/punning.c

/* Three ways to view the same bits as another type — and each one's contract. */
#include <stdint.h>
#include <stdio.h>
#include <string.h>

union bits { float f; uint32_t u; };

static void show(const char *how, uint32_t u)
{
    printf("  %-28s 0x%08X  (sign %u, exponent %3u, fraction 0x%06X)\n",
           how, u, u >> 31, (u >> 23) & 0xFFu, u & 0x7FFFFFu);
}

int main(void)
{
    float f = 1.5f;
    printf("reading the bits of the float %.1f as a 32-bit integer\n\n", (double)f);

    /* (1) a union — allowed in C.
         Reading a member other than the last one written reinterprets the
         representation. */
    union bits b = { .f = f };
    show("through a union", b.u);

    /* (2) memcpy — inside the contract everywhere; folds to one instruction */
    uint32_t u;
    memcpy(&u, &f, sizeof u);
    show("through memcpy", u);

    /* (3) a pointer cast — *only this one is outside the contract* (strict
         aliasing, chapter 37). The value may look right, but the compiler is
         entitled to reorder. The line below is shown, not used. */
    puts("  through a pointer cast       *(uint32_t *)&f — outside the contract, not run");

    puts("\n[the other direction is the same]");
    union bits c = { .u = 0x40490FDBu };   /* bits close to pi */
    printf("  0x40490FDB seen as a float is %.7f\n", (double)c.f);
    float g;
    memcpy(&g, &c.u, sizeof g);
    printf("  and through memcpy            %.7f\n", (double)g);

    puts("\n[where C and C++ part]");
    puts("  C   : reading another union member is allowed (reinterpretation).");
    puts("  C++ : reading a member that is not the active one is undefined.");
    puts("  In a header shared by both languages, memcpy is the safe spelling.");

    puts("\n[careful: not every bit pattern is a value]");
    union bits nan_bits = { .u = 0x7FC00000u };
    printf("  0x7FC00000 → %f (NaN)\n", (double)nan_bits.f);
    puts("  Integers and floats are usually harmless, but some types have trap");
    puts("  representations that must not be read.");
    return 0;
}

Output

reading the bits of the float 1.5 as a 32-bit integer

  through a union              0x3FC00000  (sign 0, exponent 127, fraction 0x400000)
  through memcpy               0x3FC00000  (sign 0, exponent 127, fraction 0x400000)
  through a pointer cast       *(uint32_t *)&f — outside the contract, not run

[the other direction is the same]
  0x40490FDB seen as a float is 3.1415927
  and through memcpy            3.1415927

[where C and C++ part]
  C   : reading another union member is allowed (reinterpretation).
  C++ : reading a member that is not the active one is undefined.
  In a header shared by both languages, memcpy is the safe spelling.

[careful: not every bit pattern is a value]
  0x7FC00000 → nan (NaN)
  Integers and floats are usually harmless, but some types have trap
  representations that must not be read.

In C it is allowed. The standard describes reading another member of a union as reinterpreting the stored representation as that member’s type. So putting in a float and reading a uint32_t to look at the bits, as in the demonstration, is inside the contract. The technique is called type punning.

In C++ it is undefined behaviour. That language’s rule is that a member which is not the active one may not be read. The same code therefore means different things in the two languages — which really does bite where a C header is included from C++.

MethodIn CNote
Reading another union memberInside the contractUndefined behaviour in C++
Moving it with memcpyInside the contract★ Safe in both languages; folds to one instruction
Casting a pointer and readingOutside the contractA strict-aliasing violation (chapter 37)

Table 49.1

The middle row is the answer. memcpy looks slow, but a small copy whose size is known at compile time folds into a single register move — as the union and the memcpy gave the same value in the demonstration, the machine code is usually the same too.

A common misconception. *(uint32_t *)&f is the most direct and the fastest”

It is the most dangerous. This accesses an object of one type through another and so breaks the strict aliasing rule (chapter 37). The compiler is entitled to assume “the float that was written and the uint32_t that was read are different objects”, and to reorder the two operations on that assumption.

The result is the familiar pattern — it works at -O0 and is wrong at -O2. And if the address is not aligned, a strict machine dies on the spot.

Some codebases quiet the compiler with -fno-strict-aliasing (the Linux kernel does), but that is paying with a whole level of optimisation. In new code, use memcpy.

Q. So does every bit pattern become a value?

A. No. The reinterpreted bits may not be a valid representation of that type. The standard calls such a thing a trap representation, and says that reading such a value is itself outside the contract.

In practice, punning between integers and floating point is mostly harmless — on today’s machines the unsigned integer types have no trap representations, and any bit pattern is a value in IEEE 754 (a number, an infinity or a NaN). The end of the demonstration shows that NaN.

The places to be careful are elsewhere: pointers punned to integers and back (chapter 37′s provenance), a bool holding bits that are neither 0 nor 1, and enumerations.

48.3.1 The size and alignment of a union, and the common initial sequence

WhatThe rule
SizeEnough for the largest member; alignment may make it larger
AlignmentThe maximum of the members’ alignments
AddressEvery member starts at the same address — the union’s own
InitialisationOne initialiser initialises the first member; designated initialisers choose

Table 49.2

One more rule matters when writing tagged unions. If several structs share a common initial sequence — leading members matching in type, one for one — then wherever the union’s declaration is visible, that common part may be read through any member.

union shape {
    struct { int kind; double r; }        circle;   /* both start with int kind */
    struct { int kind; double w, h; }     rect;
};
/* s.circle.kind and s.rect.kind name the same place */

That rule is what makes the “look at the kind first, then branch” pattern legal. The conditions are fussy, though — the complete declaration must be visible and the types of the common part must match exactly. In practice it is commoner, and safer, to keep the tag outside the union: a struct holding a kind beside it.

48.4 Bit fields — cutting up one word

Write a colon and a number after a struct member and it becomes a bit field — you specify directly how many bits that member occupies. Overlay a union on that and you have both “the eye that sees it whole as one word” and “the eye that sees it divided into fields” at once.

examples-en/ch48/bitfield.c

#include <stdio.h>
#include <stdint.h>

/* A shape common in practice: one device register word divided into fields,
   with the same memory also seen whole as a 32-bit integer. */
union control_reg {
    uint32_t raw;                 /* the eye that reads and writes it whole */
    struct {
        uint32_t enable   : 1;    /* one bit */
        uint32_t mode     : 3;    /* three bits */
        uint32_t priority : 4;
        uint32_t reserved : 8;
        uint32_t counter  : 16;
    } f;                          /* the eye that sees fields */
};

/* the practical pattern of a union inside a struct: a tag plus the content */
enum msg_kind { MSG_INT, MSG_TEXT, MSG_POINT };

struct message {
    enum msg_kind kind;           /* the tag telling which eye to look through */
    unsigned      flags : 4;      /* a few small states */
    unsigned      urgent : 1;
    union {                       /* an anonymous union (C11) */
        int  number;
        char text[16];
        struct { int x, y; } point;
    };
};

static void show(const struct message *m)
{
    printf("kind=%d flags=%u urgent=%u -> ", (int)m->kind, m->flags, m->urgent);
    switch (m->kind) {
        case MSG_INT:   printf("number %d\n", m->number); break;
        case MSG_TEXT:  printf("text \"%s\"\n", m->text); break;
        case MSG_POINT: printf("point (%d, %d)\n", m->point.x, m->point.y); break;
    }
}

int main(void)
{
    union control_reg r = { .raw = 0 };
    r.f.enable = 1;
    r.f.mode = 5;
    r.f.priority = 9;
    r.f.counter = 1000;

    printf("raw   = 0x%08x\n", r.raw);
    printf("fields: enable=%u mode=%u priority=%u counter=%u\n",
           r.f.enable, r.f.mode, r.f.priority, r.f.counter);

    /* write it whole and read it as fields — the same memory through two eyes */
    r.raw = 0x000A0013u;
    printf("after raw write: enable=%u mode=%u priority=%u counter=%u\n",
           r.f.enable, r.f.mode, r.f.priority, r.f.counter);

    printf("sizeof(union control_reg) = %zu\n", sizeof r);

    struct message a = { .kind = MSG_INT, .flags = 3, .urgent = 1, .number = 42 };
    struct message b = { .kind = MSG_TEXT, .flags = 0, .urgent = 0 };
    for (int i = 0; i < 5; i++) b.text[i] = "hello"[i];
    b.text[5] = '\0';
    struct message c = { .kind = MSG_POINT, .flags = 8, .urgent = 0,
                         .point = { .x = 3, .y = -7 } };
    show(&a); show(&b); show(&c);
    printf("sizeof(struct message) = %zu\n", sizeof(struct message));
    return 0;
}

Output

raw   = 0x03e8009b
fields: enable=1 mode=5 priority=9 counter=1000
after raw write: enable=1 mode=1 priority=1 counter=10
sizeof(union control_reg) = 4
kind=0 flags=3 urgent=1 -> number 42
kind=1 flags=0 urgent=0 -> text "hello"
kind=2 flags=8 urgent=0 -> point (3, -7)
sizeof(struct message) = 24

The first part is the typical pattern for handling a hardware register. Write to a field as in r.f.mode = 5 and the value goes into the bit positions without library help, and reading r.raw shows the result as one word. The reverse — writing r.raw whole and reading the fields — works too; the output’s third line is the check.

Convenient though it looks, the price in portability is large, because much is not fixed by the standard.

A common misconception. “Bit fields can represent a file or network format directly”

The commonest misunderstanding, and a fixture of portability accidents. A file format or protocol has the layout of its bytes and bits fixed by specification, whereas a bit field’s layout is fixed by the implementation. Change compiler or move to another machine and the fields are read at the wrong places — worse still when endianness (the previous section) is layered on. The proper method for an external format is laying out a byte array and extracting directly with shifts and masks (chapter 7). Regard bit fields strictly as a way of saving memory within one program.

That is why bit fields are not recommended today. The reason to know the syntax nonetheless is clear — you still meet them in the register definitions of embedded SDKs, in the flag bundles of old codebases, and in kernel data structures. Be able to read them, but think twice before writing new ones is the practical instinct.

48.4.1 What the implementation decides about bit fields

Bit fields look convenient, but they are among the least portable syntax in the language. Collect what the standard leaves to the implementation and the reason is plain.

WhatThe implementation decides
The order bits are placed inFrom the low end of the storage unit or the high end
The boundary of a storage unitWhether a field may straddle one, or is pushed to the next
The signedness of an int bit fieldsigned or unsignedint x : 1; may hold −1
The permitted typesBeyond _Bool, signed int and unsigned int it is implementation-defined
Padding and alignmentPadding may appear between units

Table 49.3

One syntactic restriction goes with them — the address of a bit field cannot be taken. No &, and no pointer to it.

So the conclusion is firm. Do not parse a protocol or a file format with bit fields. There you read bytes and pull the pieces out with shifts and masks — the same reason and the same prescription as chapter 47′s serialisation. Bit fields are for saving memory inside one program only.

48.5 The practical pattern of mixing structs and unions

The latter part of the example is a different story. It is a tagged union — a struct holding a tag and a union together — the pattern named in the exchange above.

struct message {
    enum msg_kind kind;      /* the tag telling which eye to look with */
    unsigned      flags : 4; /* small states — bit fields earn their place here */
    unsigned      urgent : 1;
    union {                  /* an anonymous union (C11) */
        int  number;
        char text[16];
        struct { int x, y; } point;
    };
};

Three things are layered here. The tag, the state flags saved by bit fields, and an anonymous union (C11). With no name, members can be used one step more directly, as m->number, which makes a tagged union far more readable.

There is only one discipline and it is everything — read only the member the tag says. If kind is MSG_TEXT and you read number, it becomes the “looking through another eye” of this chapter’s first section and gives a meaningless value. So code handling such data is almost always made to pass through a single switch on the tag, like the example’s show. Gather the access in one place and the place to keep the discipline is one place too.

That sizeof(struct message) came out as 24 bytes is worth reading as well — a 4-byte tag plus the word holding the bit fields plus the 16-byte union, with padding (the previous section) added for alignment. Representation always takes a little more than what was declared.

48.5.1 One real specimen — two-byte Johab Hangul

This pattern is not only a textbook affair. When Hangul was first being put into computers, splitting one word into three parts became an actual standard — Johab (조합형, “the combining form”).

examples-en/ch48/johab.c

/* Two-byte Johab Hangul — a real case of one word split into initial,
   medial and final jamo. (Hangul appears here because the subject is an
   encoding of Hangul.) */
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* Names of the jamo. Codes 0 and 1 are fill/unused, so they stay empty. */
static const char *const CHO[32] = {
    [2]="G", [3]="GG", [4]="N", [5]="D", [6]="DD", [7]="R", [8]="M",
    [9]="B", [10]="BB", [11]="S", [12]="SS", [13]="NG", [14]="J",
    [15]="JJ", [16]="C", [17]="K", [18]="T", [19]="P", [20]="H",
};
static const char *const JUNG[32] = {
    [3]="A", [4]="AE", [5]="YA", [6]="YAE", [7]="EO",
    [10]="E", [11]="YEO", [12]="YE",
    [13]="O", [14]="WA", [15]="WAE",
    [18]="OE", [19]="YO", [20]="U", [21]="WEO", [22]="WE", [23]="WI",
    [26]="YU", [27]="EU", [28]="YI", [29]="I",
};
static const char *const JONG[32] = {
    [1]="(none)", [2]="G", [5]="N", [9]="L", [17]="M", [19]="B",
    [21]="S", [23]="NG", [29]="H",
};

/* (1) Extract with shifts and masks — the way the standard pins down */
static void split_by_shift(uint16_t w)
{
    unsigned cho  = (w >> 10) & 0x1f;
    unsigned jung = (w >>  5) & 0x1f;
    unsigned jong =  w        & 0x1f;
    printf("  shift/mask: flag=%u cho=%2u(%-2s) jung=%2u(%-3s) jong=%2u(%s)\n",
           (unsigned)(w >> 15), cho, CHO[cho] ? CHO[cho] : "?",
           jung, JUNG[jung] ? JUNG[jung] : "?",
           jong, JONG[jong] ? JONG[jong] : "?");
}

/* (2) View it with bit fields in a union — handy, but the layout is
   implementation-defined */
union johab {
    uint16_t raw;
    struct {
        uint16_t jong : 5;   /* this order is not promised by the standard */
        uint16_t jung : 5;
        uint16_t cho  : 5;
        uint16_t mark : 1;
    } f;
};

int main(void)
{
    /* The Johab codes of 가 and 한, taken from the real table. */
    const uint16_t GA  = 0x8861;   /* 1 00010 00011 00001 */
    const uint16_t HAN = 0xD065;   /* 1 10100 00011 00101 */

    printf("GA(가) = 0x%04X, HAN(한) = 0x%04X\n\n", GA, HAN);
    puts("GA (가):");  split_by_shift(GA);
    puts("HAN (한):"); split_by_shift(HAN);

    union johab u = { .raw = GA };
    printf("\nthrough bit fields: mark=%u cho=%u jung=%u jong=%u\n",
           u.f.mark, u.f.cho, u.f.jung, u.f.jong);
    puts("  (On this compiler it matched the shift/mask result, because this");
    puts("   implementation fills bits from the low end. That is not a promise.)");

    /* (3) The second byte collides with ASCII — Johab's famous trap */
    unsigned char bytes[3] = { (unsigned char)(GA >> 8),
                               (unsigned char)(GA & 0xff), 0 };
    printf("\nthe two bytes of 가: %02X %02X\n", bytes[0], bytes[1]);
    printf("  the second byte 0x%02X is ASCII '%c' — searching bytes for 'a'\n",
           bytes[1], bytes[1]);
    printf("  lands inside a character: strchr result = %s\n",
           strchr((char *)bytes, 'a') ? "found (false hit)" : "not found");
    return 0;
}

Output

GA(가) = 0x8861, HAN(한) = 0xD065

GA (가):
  shift/mask: flag=1 cho= 2(G ) jung= 3(A  ) jong= 1((none))
HAN (한):
  shift/mask: flag=1 cho=20(H ) jung= 3(A  ) jong= 5(N)

through bit fields: mark=1 cho=2 jung=3 jong=1
  (On this compiler it matched the shift/mask result, because this
   implementation fills bits from the low end. That is not a promise.)

the two bytes of 가: 88 61
  the second byte 0x61 is ASCII 'a' — searching bytes for 'a'
  lands inside a character: strchr result = found (false hit)

The design is exactly what this chapter has taught. Sixteen bits are divided into four: the leading bit marks “this is Hangul”, and the remaining fifteen are cut into three fields of five bits each — initial, medial and final jamo.

bits1514–10 / 9–5 / 4–0meaning
fieldflaginitial / medial / finalfive bits each
= 0x886112 / 3 / 1ㄱ + ㅏ + (none)
= 0xD065120 / 3 / 5ㅎ + ㅏ + ㄴ

Table 49.4

The numbers given to the jamo follow a rule. Initials run 2–20 from ㄱ to ㅎ (0 and 1 are fill and reserved), and finals start with 1 for “none” and run on to 29. Only the medials leave 8–9, 16–17 and 24–25 empty — the trace of laying the vowels out in groups of four. The gaps are visible in the example’s tables.

What it bought was clear: combining jamo let it write all 11,172 modern Hangul syllables. The rival of the time, the precomposed standard (KS C 5601-1987), listed only the 2,350 syllables in common use, which famously left ordinary names and words unwritable. Johab chose to generate syllables by rule rather than enlarge a table.

In practice. Three lessons Johab left behind

First, the second byte collides with ASCII. 가 is 88 61, and the trailing byte 0x61 is plain 'a'. Code searching bytes for 'a' therefore lands in the middle of a character — the last line of the example shows the false hit happening. Chapter 9′s “a byte is not a character” turns into a bug right here.

Second, the layout is not fixed by the standard. The example’s union happened to agree with the shift/mask result on this compiler, but only because this implementation fills bits from the low end. The rule of the previous section stands: handle external formats with shifts and masks.

Third, there is a place where rule beat table. Unicode took the same idea further and tidier. The code of a Hangul syllable is computed: 0xAC00 + (initial * 21 + medial) * 28 + final — multiplication instead of bit slicing, but the same thought that syllables are made by combining jamo. Johab itself faded (Windows 95 adopted a unified precomposed code and left it behind), yet its idea lives on inside today’s standard.

48.6 Closing Part VIII

We have the two syntaxes for making types — the struct that lays things side by side (chapter 46) and the union that lays them over one another (chapter 48). And along the way we confirmed the realities of representation (endianness, padding) with our own eyes. Part II’s background knowledge has been fully collected into syntax.

The next part is the part of precision — chapter 8′s mathematics of approximation comes down into C’s floating types (chapter 50), the perspective of the contract whose seed was planted in chapter 33 grows into error handling (chapter 51), and we meet head on the world “outside the contract” that this book has foreshadowed throughout — undefined behaviour (chapter 52).