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.