The standard modern complaint about C — that platform-dependent integer sizes were a design blunder responsible for decades of portability bugs — judges a 1970s decision by 2020s conditions. When C was designed, word sizes ran 12, 16, 18, 24, 32, 36, 48, 60 and 64 bits, characters were 6, 7, 8 or 9 bits, negative numbers came in three representations, and some machines could not address a byte at all. int meant “the natural integer of the machine” — C11 §6.2.5 still says exactly that.

  • C’s types came from BCPL and B, which were typeless: one value kind, the machine word. Bell Labs’ byte-addressed PDP-11 with floating-point hardware broke that, so char became the byte and int kept the spirit of the word
  • By 1978 it already worked. K&R 1st edition lists C running on a PDP-11 (16-bit int), IBM 370 (32-bit), Interdata 8/32 (32-bit) and Honeywell 6000 — 36-bit int, 9-bit char
  • Fixed 32-bit int would have cost real instructions: on a PDP-11 every int op becomes ADD + ADC, doubling the cost of every loop counter and array index; on a 36-bit machine the compiler would mask after nearly every arithmetic op to preserve exact wraparound
  • Ones’-complement machines (UNIVAC 1100, CDC 6600) explain why signed overflow is undefined behavior — it began as a way to avoid forcing one machine’s overflow semantics onto every other machine, not as a gift to optimizers
  • ANSI C codified minimums, not exact sizes: char 8, short/int 16, long 32, long long 64. You ask what the smallest type guaranteed to hold your range is
  • Lua 5.4 is the working exemplar: LUAI_IS32INT ((UINT_MAX >> 30) >= 3) checks whether unsigned int holds at least 32 bits; lu_byte is unsigned char because uint8_t may not exist; binary packing shifts by CHAR_BIT instead of assuming 8
  • The costs were real and the author says so: code assuming sizeof(int) == sizeof(void *) broke moving to 32- then 64-bit; LP64 vs LLP64 made long mean different things on Unix and 64-bit Windows; <stdint.h> only arrived in C99
  • Even <stdint.h> kept the philosophy — exact-width types are optional, int_least32_t and int_fast32_t are mandatory. DSPs with 16- or 32-bit CHAR_BIT are still in production

The most useful reframing here is undefined signed overflow as a portability device. That one rule explains a lot of C that otherwise looks arbitrary.

And the idea outlived the era it was designed for. Rust’s isize, Go’s int, Swift’s Int and Zig’s usize are all platform-sized — they narrowed the concept from “the machine’s natural word” to “the size of a pointer,” because indexing memory is the one place word size still matters. C23 finally mandates two’s complement, but only because the machines that justified the old rule are nearly gone: the standard followed reality after reality settled.