5.1.3 · D4C Programming

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

2,623 words12 min readBack to topic

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

char8, short16, int16, long32, long long64. 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.

Figure — Type sizes — sizeof, platform dependency, stdint.h (int32_t etc.)
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