Exercises — Type sizes — sizeof, platform dependency, stdint.h (int32_t etc.)
Level 1 — Recognition
(Can you recall the guarantee?)
L1.1
State: The C standard guarantees a minimum width for each integer type. Fill the blanks: char ≥ ___ bits, short ≥ ___ bits, int ≥ ___ bits, long ≥ ___ bits, long long ≥ ___ bits.
Recall Solution
char ≥ 8, short ≥ 16, int ≥ 16, long ≥ 32, long long ≥ 64.
Why these numbers matter: the standard promises minimums only. int might be 16, 32, or 64 bits in real life — but never fewer than 16. So the only size you may safely assume is "at least this".
L1.2
State: True or false — sizeof(char) can be a value other than 1.
Recall Solution
False. sizeof is defined to count in units of char. A char occupies "1 char" by that very definition, so sizeof(char) == 1 always, on every conforming C compiler — even on an exotic machine where a char is 9 or 16 bits wide. The number of bits can vary; the number of chars cannot.
L1.3
State: Which header must you include to get int32_t, and what does the 32 promise?
Recall Solution
#include <stdint.h>. The 32 promises exactly 32 bits, signed, two's complement, on every platform — no minimums, no "it depends". Reading the name gives you the size. See Two's complement & integer overflow for what "two's complement" means.
Level 2 — Application
(Plug real numbers into the range formula.)
L2.1
State: How many distinct values can a uint16_t hold, and what is its maximum value?
Recall Solution
bits, unsigned. Distinct values . They run , so the maximum is . Why ? 16 switches, each doubling the count: (16 times) . We start counting at 0, which is why the biggest number is one less than the count.
L2.2
State: Give the exact minimum and maximum of an int32_t.
Recall Solution
, signed two's-complement. Range .
Why the asymmetry (one more negative than positive)? Zero eats a slot on the non-negative side. The patterns split into negatives and non-negatives; but the non-negatives include , leaving only positive values. This is the INT32_MAX macro's value.
L2.3
State: UINT8_MAX is what number, and why is uint8_t the natural type for a single byte value?
Recall Solution
, unsigned: max . So uint8_t holds exactly — precisely the range of one raw byte. That is why image pixels, network bytes, and buffers use uint8_t. (Plain char might be signed, giving , which is wrong for a raw byte — see the L2 trap.)
Level 3 — Analysis
(Reason about platform-dependent behaviour and code correctness.)
L3.1
State: This program is compiled once on 64-bit Linux (LP64) and once on 64-bit Windows (LLP64). Predict every line's output on each platform.
#include <stdio.h>
int main(void) {
printf("%zu\n", sizeof(int));
printf("%zu\n", sizeof(long));
printf("%zu\n", sizeof(long long));
printf("%zu\n", sizeof(void*));
return 0;
}Recall Solution
Look at the ABI figure below — it shows exactly where the two platforms disagree.

| line | LP64 (Linux) | LLP64 (Windows) |
|---|---|---|
sizeof(int) |
4 | 4 |
sizeof(long) |
8 | 4 |
sizeof(long long) |
8 | 8 |
sizeof(void*) |
8 | 8 |
The one line that differs is long. LP64 = "Long and Pointer are 64-bit". LLP64 = "Long Long and Pointer are 64-bit" — long stays 32-bit (4 bytes) so old Windows code that assumed long == int keeps working. This single row is the whole reason int64_t exists.
L3.2
State: A function receives an array and tries to compute its length. Does it work? Why or why not?
size_t len(int a[]) {
return sizeof(a) / sizeof(a[0]);
}On LP64, if a came from int data[10]; in main, what does len(data) return?
Recall Solution
It does not give 10. An array parameter decays to a pointer — inside len, a is an int*, not an array. So sizeof(a) is the pointer size = 8 (LP64), and sizeof(a[0]) = sizeof(int) = 4. Result: . Wrong.
Why the trick works in main but not here: in main, sizeof data sees the whole real array (10 × 4 = 40 bytes), so 40/4 = 10. The moment the array is passed to a function, only a pointer travels. See Pointers and array decay. Fix: pass the length as a separate parameter.
L3.3
State: Why is printing sizeof(int) with %d a bug even though it "prints 4" on your machine?
Recall Solution
sizeof yields a size_t, which is unsigned and often 64-bit. %d tells printf to read a signed 32-bit int off the argument stack. That is a type mismatch between what you passed (size_t) and what printf reads (int) → undefined behaviour. It happens to look fine for the tiny value 4, but on other values/platforms it can print garbage or misalign later arguments. Fix: use %zu, the specifier built for size_t. See printf format specifiers and size_t and ptrdiff_t.
Level 4 — Synthesis
(Design and combine several rules.)
L4.1
State: You must define a struct for a network packet header that is byte-for-byte identical on Linux and Windows: a 2-byte port, a 4-byte IPv4 address, an 8-byte timestamp, and a 1-byte flags field. Write it, and explain each type choice.
Recall Solution
#include <stdint.h>
typedef struct {
uint16_t port; // exactly 2 bytes, unsigned (ports are 0..65535)
uint32_t ip; // exactly 4 bytes (IPv4 is 32 bits)
int64_t timestamp; // exactly 8 bytes, signed (time can be pre-1970)
uint8_t flags; // exactly 1 byte of bit-flags
} Header;Why fixed-width everywhere? If you used short/int/long, long alone is 8 bytes on Linux but 4 on Windows — the two machines would lay the bytes out differently and could not parse each other's packets. uintN_t nails the layout on every platform. Caveat: the field sizes are now fixed, but the compiler may still insert padding between fields, and byte order still differs — see Struct padding & alignment and Endianness & serializing data before you actually put this on a wire.
L4.2
State: You need a counter that must reach at least 3 billion, and you want it to be as fast as possible on whatever CPU compiles it — but you do not care about its exact width. Which stdint type do you pick, and why not int32_t or int64_t?
Recall Solution
3 billion , so a 32-bit signed type overflows. You need at least 32 unsigned bits or 33+ signed bits, and you want speed, not an exact size. Pick uint_fast32_t (≥32 bits, unsigned, the fastest such type) — or int_fast64_t if you want signed headroom.
Why not int32_t? That is signed 32-bit, max 3 billion → overflow, undefined behaviour (see Two's complement & integer overflow).
Why not int64_t? It works, but it forces 64 bits even on a chip where a 32-bit register is faster. ..._fast.. says "give me at least this range, but choose the speed-optimal width for this CPU." Exact width matters for layout (packets); it does not matter for a private in-memory counter.
L4.3
State: Write a portable one-liner that prints the number of elements in a real, locally-declared array double v[7];, and state the exact number printed on any platform.
Recall Solution
printf("%zu\n", sizeof v / sizeof v[0]);Prints 7 on every platform. Reason: sizeof v = total bytes of the whole array = , and sizeof v[0] = sizeof(double). The sizeof(double) cancels: , independent of what s actually is. This is why the idiom is portable — it never mentions a concrete size. (Works only because v is a real array here, not a decayed pointer parameter — contrast L3.2.)
Level 5 — Mastery
(Prove, generalise, and reason at the edges.)
L5.1
State: Prove from first principles that a signed -bit two's-complement integer has range , and use it to give the full range of int16_t.
Recall Solution
Proof. bits give distinct patterns (each bit is an independent switch, doubling the count). Two's complement assigns these patterns so that:
- the patterns whose top bit is represent (the non-negatives),
- the patterns whose top bit is represent (the negatives).
Adding the two groups: patterns, all used, none repeated. The smallest is ; the largest is .
For int16_t, : range .
Edge note: there is exactly one more negative than positive value ( has no positive twin). That lone extreme, , is the classic overflow landmine — negating it does not fit. See Two's complement & integer overflow.
L5.2
State: Someone claims: "On any machine, sizeof(int)*8 equals the number of bits in an int." Under what assumption is this true, and where could it fail?
Recall Solution
sizeof(int) returns the size in units of char — i.e. how many chars an int is. The number of bits is sizeof(int) * CHAR_BIT, where CHAR_BIT (from <limits.h>) is the bits per char. On every mainstream machine CHAR_BIT == 8, so sizeof(int)*8 is correct there. It can fail on historical/DSP hardware where a char is 9, 16, or 32 bits — then CHAR_BIT != 8 and you must multiply by CHAR_BIT, not 8. Mastery point: sizeof counts chars, and chars are not universally 8 bits — that assumption is the hidden one.
L5.3
State: You receive a struct whose fields are declared with fixed-width types uint16_t; uint32_t;. A colleague concludes: "Therefore sizeof(struct) is exactly ." Is that guaranteed? Explain and give the likely real value.
Recall Solution
Not guaranteed. Fixed-width types pin each field's size, but the compiler may insert padding so that each field lands on its natural alignment boundary. A uint32_t typically wants a 4-byte-aligned address, so after the 2-byte uint16_t the compiler inserts 2 padding bytes, then the 4-byte field. Layout: [u16 = 2][pad = 2][u32 = 4] → sizeof = 8, not 6. The struct is also usually padded up to a multiple of its largest alignment. Takeaway: stdint.h fixes field widths, not the whole-struct size or spacing — for that you need Struct padding & alignment (and packing pragmas) plus Endianness & serializing data when the bytes leave the machine.
Active recall (mixed)
Recall Fastest way to sanity-check any signed range: what two numbers bracket
intN_t?
and . Plug = 8, 16, 32, 64.
Recall Which single type changes size between LP64 and LLP64?
long: 8 bytes on LP64 (Linux/macOS), 4 bytes on LLP64 (Windows). Everything else in the common set matches.
Recall Why does
sizeof(v)/sizeof(v[0]) fail inside a function that took v as a parameter?
The array parameter decayed to a pointer; sizeof(v) is the pointer size, not the array size.
Connections
- 5.1.03 Type sizes — sizeof, platform dependency, stdint.h (int32_t etc.) (Hinglish)
- Two's complement & integer overflow
- Integer promotion & usual arithmetic conversions
- Pointers and array decay
- Struct padding & alignment
- Endianness & serializing data
- printf format specifiers
- size_t and ptrdiff_t