5.1.3 · D3C Programming

Worked examples — Type sizes — sizeof, platform dependency, stdint.h (int32_t etc.)

2,636 words12 min readBack to topic

This page is the "hands dirty" companion to Type sizes — sizeof, platform dependency, stdint.h (int32_t etc.). We will walk through every kind of situation the topic can throw at you: reading sizeof, the platform split, the overflow edge, the degenerate zero-width cases, a real network word-problem, and a nasty exam twist. Each example first asks you to guess, then shows every step with a reason, then verifies the number.

Before we start, one reminder in plain words: a byte here means "one char", and sizeof is a ruler that measures how many of those char-slots a thing takes. Nothing below assumes you already trust any size — we compute each from the rule that gave it.


The scenario matrix

Every worked example below is tagged with the cell of this matrix it covers. Together they touch every cell.

Cell What makes it different Covered by
A. Basic measure Read a size straight from sizeof on one known ABI Ex 1
B. Platform split Same type, different answer on LP64 vs LLP64 vs 16-bit Ex 2
C. Signed max/min Largest & smallest value that fits (both signs) Ex 3
D. Unsigned wrap (limiting value) Value at the very edge, and one step past it Ex 4
E. Zero / degenerate input Empty array, sizeof of a zero-length-ish thing, sizeof(char) Ex 5
F. Array length trick + decay trap sizeof arr / sizeof arr[0] working vs silently broken Ex 6
G. Real-world word problem Design a packet struct with fixed widths Ex 7
H. Exam twist A mixed signed/unsigned comparison that surprises everyone Ex 8
Figure — Type sizes — sizeof, platform dependency, stdint.h (int32_t etc.)

The picture above lays the same eight cells on a number line of "how many bits" so you can see where each example lives: the small toy-computer end (16-bit), the mainstream middle (32-bit), and the wide end (64-bit).


Ex 1 — Cell A: Basic measure

Steps.

  1. sizeof(char) is 1. Why this step? Because the standard defines sizeof to count in units of char. A char is one char wide — always, on every machine. This is the one size you never have to look up.
  2. sizeof(int) on LP64 is 4. Why this step? The standard only promises int ≥ 16 bits. LP64 (Linux/macOS 64-bit) chooses 32 bits = bytes. We read this from the ABI table, not from a guess.
  3. sizeof(double) is 8. Why this step? An IEEE-754 double-precision float is 64 bits by design; bytes. This is stable across the mainstream ABIs.

Answer: 1 4 8.


Ex 2 — Cell B: The platform split

Steps.

  1. LP64 → long is 8. Why this step? "LP64" literally means Long and Pointer are 64-bit. .
  2. LLP64 → long is 4. Why this step? "LLP64" means only Long-Long and Pointer are 64-bit; plain long stays 32-bit. . This is the killer detail from the parent note.
  3. 16-bit DOS → long is 4. Why this step? Old 16-bit int was 2 bytes, but long still had to satisfy the "≥ 32 bits" floor, so it was 4.

Answer: 8, 4, 4.

Figure — Type sizes — sizeof, platform dependency, stdint.h (int32_t etc.)

The figure shows the three memory boxes side by side: notice the LP64 box is twice as wide as the LLP64 box for the identical type name long. That visual mismatch is exactly the bug that bites cross-platform code.


Ex 3 — Cell C: Signed max and min

Steps.

  1. Count the patterns: 16 switches → distinct bit patterns. Why this step? Every bit is an independent on/off switch; each doubles the count, giving . This is the from-scratch derivation the parent used.
  2. Split for signed two's complement: half go to negatives, half to non-negatives. Range with . Why this step? The top bit acts as a sign region; the patterns split into negative and non-negative (which includes zero). See Two's complement & integer overflow for why the negatives get the exact half they do.
  3. Plug in: max , min . Why this step? The non-negative side spends one pattern on zero, so its top is ; the negative side has no zero to spend, so it reaches one further, to . They are not symmetric — there is one more negative than positive.

Answer: max 32767, min -32768.


Ex 4 — Cell D: Unsigned wrap at the limiting value

Steps.

  1. Range of uint8_t: . Why this step? Unsigned counts from 0, so the top is . is sitting exactly on the ceiling.
  2. Compute — but 256 needs a 9th bit, which does not exist in 8 bits. Why this step? We must check whether the mathematical result still fits; it does not, so the language rule for unsigned takes over.
  3. Unsigned overflow wraps modulo : result . Why this step? The C standard defines unsigned arithmetic as arithmetic modulo — the extra high bit is simply discarded, like an odometer rolling from 999 to 000. (Careful: because of Integer promotion & usual arithmetic conversions, x + 1 is first computed as int, giving 256, then the assignment back to uint8_t performs the modulo. The final stored value is still 0.)

Answer: 0.


Ex 5 — Cell E: Zero / degenerate inputs

Steps.

  1. (a) sizeof(char) is 1, by definition, forever. Why this step? sizeof measures in units of char, so char measures 1 of itself. Even on a hypothetical machine where a char is 9 or 16 bits, sizeof(char) is still 1 — it counts chars, not bits.
  2. (b) A zero-length array has 0 elements × element-size = 0 bytes, so sizeof a is 0 on compilers that allow it. Why this step? Total size = count × per-element size = . This is the degenerate "empty ruler" case — it multiplies down to nothing.
  3. (c) An empty struct is not legal in standard C (it is in C++, where sizeof would be 1). In C you must give a struct at least one member. Why this step? The scenario matrix demands we cover the illegal degenerate input too, not just the small one — the correct answer here is "this doesn't compile", not a number.

Answers: (a) 1; (b) 0; (c) compile error in standard C.


Ex 6 — Cell F: Array-length trick and the decay trap

Steps.

  1. In main: a is a real array of 10 int. sizeof(a) = 10 × 4 = 40, sizeof(a[0]) = 4. Why this step? a genuinely occupies 40 contiguous bytes, and the ruler sees all of them.
  2. Divide: . Correct element count. Why this step? Total bytes ÷ bytes-per-element = number of elements — a size-independent way to get length.
  3. In f: the parameter int arr[] is a lie — C rewrites it to int *arr. So sizeof(arr) is the size of a pointer = 8 on LP64, and sizeof(arr[0]) is still 4. Why this step? Arrays passed to functions decay to pointers — see Pointers and array decay. The array's length is not carried along; only its first address is.
  4. Divide: . Wrong — it is not 10. Why this step? The trick silently returns "pointer-size ÷ int-size", a meaningless 2, because the array's true length is gone. Fix: pass the length as a separate argument.

Answers: in main: 10, in f: 2 (on LP64).


Ex 7 — Cell G: Real-world word problem (packet struct)

Steps.

  1. Port range 0–65535 = → needs exactly 16 unsigned bits → uint16_t (2 bytes). Why this step? , so 16 unsigned bits fit it perfectly; a smaller uint8_t (max 255) can't hold a port.
  2. IPv4 address = 32-bit number → uint32_t (4 bytes). Why this step? An IPv4 address is defined as exactly 32 bits (four octets). Fixed width guarantees both machines lay the same 4 bytes.
  3. Timestamp, possibly large and negative → int64_t (8 bytes). Why this step? Negative needs signed; "large" (e.g. Unix nanoseconds) overflows 32 bits, so we choose 64.
  4. Add the data sizes: bytes. Why this step? We sum the guaranteed widths. Every platform agrees on this data total because every field is fixed-width — the whole reason we avoided int/long.

Answer: struct with uint16_t, uint32_t, int64_t; 14 bytes of data.


Ex 8 — Cell H: The exam twist (signed vs unsigned compare)

Steps.

  1. Spot the mixed types: a is signed int, b is unsigned int. C must convert them to a common type before comparing. Why this step? Comparison needs both operands the same type; C applies the usual arithmetic conversions — see Integer promotion & usual arithmetic conversions.
  2. The rule: when signed and unsigned int meet (same rank), the signed operand is converted to unsigned. Why this step? Unsigned "wins" at equal rank. So a = -1 is reinterpreted as an unsigned value.
  3. Convert to unsigned int (32 bits) modulo : . Why this step? Two's-complement all-ones is ; converting a negative to unsigned adds until it's in range. So a becomes a huge number.
  4. Compare ? False. So the else runs. Why this step? After conversion the comparison is between two unsigned values, and is not less than .

Answer: prints not less — the surprising branch.


Recall — cover the whole matrix

Recall Which cell does each idea live in?

Basic read → A. Same type, different size across ABIs → B. Signed max/min asymmetry → C. Unsigned edge wrap → D. Empty/illegal degenerate → E. Length trick vs decay → F. Packet design → G. Signed/unsigned compare surprise → H.

Recall Why does Ex 8 not print "less"?

The signed -1 is converted to unsigned (becomes ) before the comparison, so it is larger than 1, not smaller.

Recall Why is

int16_t min -32768 but max only 32767? Two's complement has one more negative than positive: , because the non-negative side must spend one pattern on zero.


Connections