5.1.24 · D5C Programming

Question bank — Unions — overlapping memory

1,400 words6 min readBack to topic

The reveals use one convention: after ::: you get a verdict + the reasoning, never a bare "true"/"false"/"yes"/"no".


True or false — justify

All members of a union always start at the same address
True. Every member begins at offset 0 of the union; that shared start is the whole point — it is why they overlap.
Reading a union member you did not last write gives a compiler error
False. It compiles and runs fine; it just reinterprets whatever bytes are there as that member's type, so you get meaningless (but legal-to-execute) data.
sizeof(union U) can be larger than its largest member
True. Alignment can round the size UP so arrays of the union stay aligned — see Memory alignment and padding. It is never smaller than the largest member, though.
Writing one union member and then writing another leaves the first member intact
False. They share the same bytes; the second write overwrites (reinterprets) the very storage the first used, so the first is gone.
A union member of type char c[4] and a member int i refer to the same four bytes
True. Both start at offset 0 and both span 4 bytes, so c[0..3] and i are literally the same memory — this is the basis of type punning.
union U { int i; double d; }; has sizeof == 12 because 4 + 8 = 12
False. That is the struct rule (sum). A union's size is the LARGEST member (8 here), possibly rounded for alignment — not the sum.
A union can hold a valid int and a valid float simultaneously
False. Only the most recently written member is meaningful; the other is just the same bytes reinterpreted, so "both valid at once" is impossible.
Initializing union Data d = {65}; sets every member to 65
False. It initializes only the FIRST member (here d.i = 65); the other members are simply the same bytes, meaningful only if you read them as the first member's type.

Spot the error

union U { int i; float f; };
union U u;
u.i = 65;
float x = u.f;   // "convert int 65 to float"
What is wrong?
This does NOT convert — it reinterprets the bit pattern of the int 65 as a float, giving a tiny garbage value. For a real conversion write float x = (float)u.i;.
union U { char c; int i; };
union U u;
u.c = 'A';
printf("%d\n", u.i);   // expecting 65
Why is u.i not reliably 65?
You wrote only 1 byte (u.c); the other 3 bytes of i are uninitialised. Reading i reads all 4 bytes, so the top 3 are garbage — the result is not guaranteed to be 65.
struct Variant { enum {INT, FLOAT} tag; union { int i; float f; } val; };
struct Variant v;
v.val.i = 10;
// tag never set
if (v.tag == FLOAT) printf("%f\n", v.val.f);
What went wrong?
The tag was never assigned, so v.tag holds garbage and the branch is unpredictable. In a tagged union YOU must set the tag every time you write a member, or the bookkeeping is a lie.
union U { int i; char c[4]; } u;
u.i = 0x41424344;
printf("%c\n", u.c[0]);   // "must be 'A'"
Why is 'A' not guaranteed?
It depends on Endianness — byte order. On little-endian c[0] is the low byte 0x44 ('D'); only on big-endian is it 0x41 ('A'). The comment assumes a byte order it never checked.
union U { int i; float f; };
union U a = { .f = 3.14f };
printf("%d\n", a.i);   // "read what I stored"
Is reading a.i here defined and meaningful?
It reads the raw float bits as an int — legal to run, but the number is the IEEE-754 representation of 3.14, not 3, so it is meaningful only as a bit pattern, not as a value.

Why questions

Why is a union's size the largest member and not the sum?
Every member starts at offset 0 and overlaps the others, so the storage only needs to be big enough for the single worst-case (biggest) member — never all of them at once.
Why does a union not "remember" which member is currently active?
A union is just raw overlapping bytes with no hidden flag; the same bit pattern can be read through any member, so nothing in the type records which write was last. You track it yourself with a tag — see Enums.
Why do unions exist at all when structs already bundle data?
Two reasons the parent stressed: to save memory (one slot instead of many) and to reinterpret the same bytes as different types on purpose (Type punning and bit-level reinterpretation).
Why can sizeof(union U) exceed even the largest member?
Alignment padding: the size is rounded up to a multiple of the strictest member's alignment so that an array of the union keeps every element correctly aligned. Use the sizeof operator to see the real value.
Why is (float)d.i different from reading d.f after d.i = 65?
The cast performs an arithmetic conversion producing 65.0f (correct value). Reading d.f copies the bits of the integer 65 and pretends they are a float — same bytes, completely different meaning.
Why does endianness matter for unions but the parent's sizing rule does not mention it?
Sizing only depends on member sizes and alignment. Endianness only affects which byte lands where when you punning-read the overlapping bytes, so it matters for interpretation, not for sizeof.

Edge cases

What is sizeof a union containing only char c;?
It is at least 1 (the largest member is 1 byte); alignment of char is 1, so no padding is forced — sizeof == 1.
What happens if you read a union member immediately after declaring the union with no initialization?
You read indeterminate bytes — the storage holds whatever was in that memory, so any member you read is garbage until you write one.
For union U { char c[3]; int i; };, why might sizeof be 4 even though the largest declared member is char c[3] (3 bytes)?
int requires 4-byte alignment, so the union's size is rounded up to 4; the biggest member by size is int (4) anyway, so it fits — the answer is 4.
If all members of a union have the same size (e.g. int i; float f; char c[4];), does the size just equal that common size?
Yes — the max of equal sizes is that size (4 here), and if its alignment divides it (4 divides 4) no extra padding is added, so sizeof == 4.
Can a union member be a struct, and does that change the sizing rule?
Yes, a member can be a struct; the rule is unchanged — the union's size is the largest member's size (the struct's full size, padding included) rounded for alignment.
Is it defined behaviour to read the "common initial sequence" of two union members that are structs sharing the same leading fields?
Yes — C explicitly allows reading the shared leading members of two structs in a union if their initial field types match, one of the few portable punning cases.

Connections

  • Structs — separate memory for each member
  • Memory alignment and padding
  • Endianness — byte order
  • Type punning and bit-level reinterpretation
  • Enums
  • sizeof operator