Frontend Labs
⋯

3. Types and the sizes the standard actually guarantees

3.1 The trap: "int is 4 bytes" is a habit, not a guarantee

The C standard only guarantees minimum ranges, not exact sizes: char ≥ 8 bits, short/int ≥ 16 bits, long ≥ 32 bits, long long ≥ 64 bits, and sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long). On the platforms you use daily this happens to be 1/2/ 4/8/8 bytes, but code that assumes int is exactly 32 bits everywhere has already broken on real hardware (16-bit int on some embedded targets; long is 4 bytes on Windows LLP64 but 8 bytes on Linux/macOS LP64 for the same "64-bit" build).

The fix, always: use <stdint.h> for anything where the size matters.

#include <stdint.h>
#include <stdio.h>

int main(void) {
    int32_t  a = 42;          // exactly 32 bits, everywhere, or it fails to compile
    uint64_t b = 1ULL << 40;  // exactly 64 bits, unsigned
    size_t   n = sizeof(a);   // the *correct* type for sizes/counts — unsigned, platform width
    intptr_t p;               // an integer wide enough to hold a pointer, for pointer arithmetic tricks

    printf("%d %llu %zu\n", a, (unsigned long long)b, n);
    return 0;
}
Type Guarantee Use it for
int8_t/uint8_t … int64_t/uint64_t exact width wire protocols, file formats, anywhere size matters
size_t unsigned, wide enough for any object's size array indices, sizeof, loop counters over memory
ssize_t (POSIX) signed version of size_t return values that need a -1 error sentinel (read(), write())
ptrdiff_t signed, holds the result of pointer subtraction pointer arithmetic results
intptr_t/uintptr_t can hold a void* round-trip rare — casting pointers to integers for bit tricks or logging

3.2 printf/scanf format specifiers, correctly

#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>

int main(void) {
    size_t   n = 100;
    int64_t  big = -123456789012345;
    uint32_t u = 4000000000u;

    printf("n=%zu big=%" PRId64 " u=%" PRIu32 "\n", n, big, u);
    // %zu for size_t, PRId64/PRIu32 macros from <inttypes.h> for fixed-width types —
    // never guess "%ld" for int64_t; it's `long` on Linux but `long long` on Windows.
    return 0;
}

War story. Mismatched printf format specifiers are UB, not just "ugly" — printf("%d", some_int64) reads only 4 bytes off a variadic argument that's actually 8, so it sometimes reads the second half of your argument, garbage stack memory, or the wrong subsequent argument entirely, depending on the ABI's argument-passing register layout on that call. -Wformat (in -Wall) catches this at compile time — never disable it.

3.3 Signed integer overflow is undefined behavior — unsigned is not

#include <limits.h>
#include <stdio.h>

int main(void) {
    int x = INT_MAX;
    // int y = x + 1;      // UB — compiler may assume this never happens and
                            // optimize based on that assumption (see Part V)
    unsigned int u = UINT_MAX;
    unsigned int v = u + 1; // well-defined: wraps to 0, per modular arithmetic rules
    printf("%u\n", v);      // 0
    return 0;
}

This single asymmetry (signed overflow = UB, unsigned overflow = defined wraparound) is behind a large fraction of real-world security bugs and is covered in depth in Part V.


Generated from real-life-c-guide.md · 8 Oct 2026 16:36