Exercises — Unions — overlapping memory
Assume throughout, unless a problem says otherwise:
char= 1 byte,short= 2 bytes,int= 4 bytes,float= 4 bytes,double= 8 bytes, and a pointer = 8 bytes (a typical 64-bit machine).- Alignment of a type = its size for these basic types (so a
doublewants an address that is a multiple of 8). - Little-endian byte order (lowest-value byte stored at the lowest address) unless stated. See Endianness — byte order.

The figure above is the mental model behind almost every answer: all members overlap starting at byte 0, and the total width is the widest member (rounded up for alignment).
Level 1 — Recognition
(Can you state the rule and read it off a definition?)
L1.1
State, in one sentence, where each member of a union begins in memory.
Recall Solution
Every member begins at the same address, offset 0. They physically overlap — there is no separate space per member (that would be a struct).
L1.2
For
union A { char c; short s; int i; };give sizeof(union A).
Recall Solution
Sizes are char=1, short=2, int=4. WHY largest? The room must fit whichever member is stored, so it must fit the worst case — the int. Largest = 4, and 4 is already a multiple of int's alignment (4).
L1.3
True or false: in a union you may read all members at once after writing one.
Recall Solution
False. Only the member you last wrote holds meaningful data. Reading any other member reads the same bytes reinterpreted — usually garbage. Reading a different member deliberately is type punning, not "reading them all".
Level 2 — Application
(Apply the size and offset rules to concrete layouts.)
L2.1
Compute sizeof for
union B { char c[10]; int i; double d; };Recall Solution
Member sizes: char c[10] = 10, int = 4, double = 8. Largest raw size = 10 (the array).
WHY alignment matters here: the strictest member is double, alignment 8. The size must be rounded up to a multiple of 8 so that an array of these unions keeps every double 8-aligned.
So even though the biggest member is 10 bytes, the union is 16 bytes.
L2.2
union C { unsigned char b[4]; int i; };
union C u;
u.i = 65;On little-endian, list the four bytes u.b[0..3] in hex, and give u.b[0] as a character.
Recall Solution
. Little-endian stores the lowest-value byte first:
ASCII 0x41 is 'A', so u.b[0] prints A.
L2.3
Same union, same u.i = 65, but now the machine is big-endian. Give u.b[0].
Recall Solution
Big-endian stores the highest-value byte first:
So u.b[0] is 0x00 (the NUL character), not 'A'. This is exactly why the character trick from L2.2 is endianness-dependent.
Level 3 — Analysis
(Trace what the bits actually become when reinterpreted.)
L3.1
union D { int i; float f; } d;
d.f = 2.0f;
printf("%d", d.i); // ?Predict the printed integer. Use IEEE-754 single precision.
Recall Solution
WHY not just "2"? Reading d.i after writing d.f does no arithmetic conversion — it reads the raw float bit pattern as an integer.
The IEEE-754 bits for : sign , exponent field (biased: ), mantissa . Assembling:
As an integer that is . So it prints 1073741824, not 2.
L3.2
Reverse it: which float value has the exact same 4-byte pattern as the integer 1078530011?
Recall Solution
. Decode as IEEE-754:
- sign
- exponent field actual exponent
- mantissa bits , giving fraction , so significand
This is the well-known "float bits of ". It demonstrates that a union just relabels bytes — the number's meaning changes entirely with the member you read.
L3.3
union E { short s; unsigned char b[2]; } e;
e.b[0] = 0x34;
e.b[1] = 0x12;
printf("%x", e.s & 0xFFFF); // little-endianWhat is printed?
Recall Solution
Little-endian assembles the value as (high byte << 8) | low byte, where the low byte is b[0]:
Prints 1234. Notice we wrote the bytes "backwards" (34 then 12) to get the number 0x1234 — that reversal is little-endianness.
Level 4 — Synthesis
(Design layouts and safe patterns yourself.)
L4.1
Design a struct RGBA overlaid on a single uint32_t so you can access the whole color as one 32-bit number and its four channels r,g,b,a individually. Assume little-endian, with r at the lowest byte. Write the union.
Recall Solution
union Color {
unsigned int rgba; // whole 32-bit value
struct { unsigned char r, g, b, a; } ch; // four channels
};WHY a struct inside the union? The four channels must live simultaneously (they are separate bytes), so they need separate memory — that is a struct. But that whole 4-byte struct overlaps the unsigned int, so both views share the same bytes.
On little-endian, ch.r is byte 0 = lowest 8 bits of rgba. So rgba = 0x11223344 gives ch.r = 0x44, ch.g = 0x33, ch.b = 0x22, ch.a = 0x11.
L4.2
Build a tagged union Number that can hold either a 64-bit integer or a double, and write a function print_number that reads only the active member. Explain why the tag is essential.
Recall Solution
struct Number {
enum { NUM_INT, NUM_DBL } tag; // who is live?
union { long long i; double d; } val;
};
void print_number(struct Number n) {
if (n.tag == NUM_INT) printf("%lld\n", n.val.i);
else printf("%f\n", n.val.d);
}WHY the tag? A union has no runtime memory of which member was last written. The enum tag is your bookkeeping so print_number reads only the member that was actually stored; reading the other would reinterpret unrelated bits. See the tagged-union pattern in the parent note.
L4.3
Give the sizeof of struct Number from L4.1 above (the tagged one) and explain the padding.
Recall Solution
The union { long long i; double d; } is 8 bytes (both members are 8, alignment 8). The enum is an int = 4 bytes.
Layout: tag at offset 0 (4 bytes), then the union needs 8-byte alignment, so 4 bytes of padding are inserted, then the union at offset 8.
The whole struct's alignment is 8 (from the union), and 16 is already a multiple of 8, so no trailing padding. See Memory alignment and padding.
Level 5 — Mastery
(Edge cases, degenerate inputs, and reasoning under alignment.)
L5.1
What is sizeof a union with exactly one member, union U { double d; }? And what is the size of a union that (hypothetically) had zero members?
Recall Solution
One member: size = that member = sizeof(double) = 8. There is no "sum vs largest" ambiguity with a single lodger.
Zero members: an empty union is not valid in standard C — a union must have at least one named member. (Some compilers allow it as an extension with size 0 or 1, but you should not rely on it.)
L5.2
union F { char c; int i; };
union F u;
u.c = 'X';
printf("%d", u.i); // ?Explain precisely why the printed integer is unspecified, even though we did write something.
Recall Solution
We wrote only 1 byte (u.c), but reading u.i reads 4 bytes. Byte 0 is 'X' = 0x58; the other 3 bytes were never written, so they hold indeterminate ("garbage") values left over in memory.
Therefore u.i is some number of the form 0x??????58 (little-endian) where the ? bytes are unknown. The result is unspecified — the only reliably-set byte is the one u.c touched. Never read a wider member than the one you initialized.
L5.3
union G { char c[3]; short s; }; // char[3] = 3 bytes, short = 2, align(short)=2Compute sizeof(union G) and justify with alignment.
Recall Solution
Raw sizes: c[3] = 3, short = 2. Largest raw = 3. Strictest alignment = short's = 2.
Round 3 up to the next multiple of 2:
So the union is 4 bytes, not 3 — padding of 1 byte is added so arrays of union G keep every short on an even address.
L5.4
Sketch the byte layout (offsets) when this union stores a double, for union H { char tag; double d; };. What is its size, and where does tag sit relative to d?
Recall Solution
Both members start at offset 0 — they overlap. So tag and byte 0 of d are the same byte. Storing d overwrites tag; storing tag corrupts the low byte of d.
Size = largest member double = 8, alignment 8, and 8 is already a multiple of 8, so:
Key contrast with a struct: in a struct H the two would be at different offsets (0 and 8, with padding), totalling 16 bytes; in the union they share offset 0 and take 8. This is the whole memory-saving point of unions.
Recall Quick self-check (cloze)
A union's members all start at offset 0. A union's size equals its largest member, rounded up for alignment. Reading a member you did not write gives you reinterpreted / garbage bits, not a converted value. A tagged union pairs the union with an enum recording the live member.
Connections
- Unions — overlapping memory (parent)
- Structs — separate memory for each member
- Memory alignment and padding
- Endianness — byte order
- Type punning and bit-level reinterpretation
- Enums
- sizeof operator