The C Path — learn C, visuallyprintable cheatsheet

stdint.h, limits.h & inttypes.h — know your integers

Facts per ISO C17 · C23 changes marked · UB = undefined behavior, ID = implementation-defined

Styled for paper — hit Ctrl+P and pin it above your desk.

"How big is an int?" The honest C answer: it depends — the standard only guarantees minimums. On 64-bit Linux long is 64 bits; on 64-bit Windows it's 32. These headers turn that chaos into certainty.

<stdint.h> — fixed-width types (C99)

typemeaningmin / max macrosprint with
int8_t / uint8_texactly 8 bits, two's complement (C23 mandates two's complement; the typedefs are optional only on exotic hardware)INT8_MIN INT8_MAX / UINT8_MAXPRId8 / PRIu8, hex PRIx8
int16_t / uint16_texactly 16 bitsINT16_MIN INT16_MAX / UINT16_MAXPRId16 / PRIu16
int32_t / uint32_texactly 32 bitsINT32_MIN INT32_MAX / UINT32_MAXPRId32 / PRIu32
int64_t / uint64_texactly 64 bitsINT64_MIN INT64_MAX / UINT64_MAXPRId64 / PRIu64
int_least8_tuint_least64_tsmallest type with at least N bits — always existsINT_LEAST8_MINPRIdLEAST8
int_fast8_tuint_fast64_tfastest type with at least N bits (often plain int — or a full register — under the hood)INT_FAST8_MINPRIdFAST8
intptr_t / uintptr_tinteger wide enough to round-trip a pointer — the only sanctioned way to store an address as an integer (optional but ubiquitous)INTPTR_MIN INTPTR_MAX / UINTPTR_MAXPRIdPTR / PRIxPTR
intmax_t / uintmax_tthe widest integer typeINTMAX_MIN INTMAX_MAX / UINTMAX_MAXPRIdMAX, or %jd / %ju

Constant macros: INT8_C(42), UINT64_C(0xFFFFFFFFFF) make a literal of (at least) the right type without guessing suffixes. Also in stdint.h: SIZE_MAX, PTRDIFF_MIN/MAX, WCHAR_MIN/MAX, WINT_MIN/MAX, SIG_ATOMIC_MIN/MAX. C23 adds *_WIDTH macros (bit widths) for every type.

<limits.h> — what this machine's basic types hold

typestandard guarantees at leasttypical 64-bit Linux (LP64)macroslearn
char8 bits (CHAR_BIT ≥ 8); signedness is ID!8 bits, signedCHAR_BIT CHAR_MIN CHAR_MAX→ lesson
signed char / unsigned char−127..127 / 0..255−128..127 / 0..255SCHAR_MIN SCHAR_MAX UCHAR_MAX
short16 bits (−32767..32767)16 bitsSHRT_MIN SHRT_MAX USHRT_MAX
int16 bits — yes, really (alive and well on microcontrollers)32 bitsINT_MIN INT_MAX UINT_MAX→ lesson
long32 bits64 bits — but 32 on 64-bit Windows!LONG_MIN LONG_MAX ULONG_MAX
long long64 bits (C99)64 bitsLLONG_MIN LLONG_MAX ULLONG_MAX

Minimum magnitudes read ±32767, not ±32768, because C17 still permitted sign-magnitude and ones'-complement machines; C23 mandates two's complement (negative-numbers lesson). The portable bit-width pattern: sizeof(type) * CHAR_BIT. Float limits (FLT_EPSILON, DBL_MAX…) live in <float.h> — covered in the limits-stdint lesson.

<inttypes.h> — printing fixed-width types

What printf specifier matches int64_t? On 64-bit Linux it's long (%ld), on Windows long long (%lld) — hardcode either and the other platform is UB. The PRI macros expand to the right letters and glue in via string-literal concatenation:

build the macro namepiecesexamples
printf: PRI + conversion + widthconversion: d i u o x X (C23: b) · width: 8 16 32 64, LEAST8…, FAST8…, MAX, PTRPRId64 PRIu32 PRIx8 PRIXMAX PRIxPTR
scanf: SCN + conversion + widthconversion: d i u o xSCNd64 SCNu32 SCNx16
usagenotes
printf("big = %" PRId64 "\n", big);the macro is a string literal — adjacent literals concatenate at compile time
printf("flags = 0x%08" PRIX32 "\n", f);flags, width and precision still go before the macro
scanf("%" SCNd64, &big);SCN macros size the pointer target correctly
strtoimax / strtoumax / imaxabs / imaxdivalso live in inttypes.h — intmax_t cousins of strtol/abs/div

size_t & ptrdiff_t (<stddef.h>)

typewhat it isprinttraplearn
size_tunsigned type of sizeof and array indexing; max is SIZE_MAX%zuit can't go negative: for (size_t i = n-1; i >= 0; --i) loops forever. Count down with for (size_t i = n; i-- > 0;)→ lesson
ptrdiff_tsigned result of subtracting two pointers into the same array%tdsubtracting pointers into different objects is UB→ lesson

ssize_t is POSIX, not ISO C. Also in stddef.h: NULL, offsetof(type, member), max_align_t; C23 adds nullptr and makes bool/true/false real keywords (historically via <stdbool.h>).

Integer promotions & conversion rank — the 60-second version

rulestatement
rank ladder_Bool < char < short < int < long < long long (an enum has the rank of its compatible type)
integer promotionsanything ranked below int becomes int first (or unsigned int if int can't hold every value). Applies to arithmetic, shifts, unary + - ~, and variadic calls (where float also becomes double)
usual arithmetic conversionsafter promotion: same type → done · same signedness → lower rank converts up · mixed signedness: if the unsigned side's rank ≥ the signed side's, both go unsigned; if the signed type can represent every unsigned value (e.g. long vs unsigned int on LP64), both go signed; otherwise both go to the unsigned version of the signed type
signed → unsignedalways defined: value wraps modulo 2ⁿ ((unsigned)-1 == UINT_MAX)
unsigned → signed, value doesn't fitID result (or an implementation-defined signal) — in practice it wraps, but the standard doesn't say so
signed arithmetic overflowUBINT_MAX + 1 is not "wraps around", it's undefined. Unsigned arithmetic wraps by definition
expressionresultwhylearn
-1 > 1utrue(!)same rank, mixed signs → −1 converts to unsigned, becoming 4294967295→ lesson
sizeof(int) > -1false(!)sizeof yields size_t; −1 converts to SIZE_MAX→ lesson
'A' + 1int 66two chars added = the addition happens in int→ lesson
uint8_t u = 0xFF; ~u == 0x00false(!)~u promotes to int first: ~255 is −256, not 0. Fix: (uint8_t)~u→ lesson
unsigned char u = 255; u << 1510promotion to int happens before the shift — no 8-bit wraparound→ lesson
unsigned short a = 65535, b = 65535; a * bUB on 32-bit-int platforms(!)both promote to signed int; 65535 × 65535 overflows INT_MAX. Cast one side: (unsigned)a * b→ lesson

Which type do I use?

situationusewhy
exact bit layout — file format, network packet, hardware registeruint32_t, int64_t, … (stdint.h)same size on every machine on Earth
a size, length, or array indexsize_tmatches sizeof, malloc, strlen; print with %zu
values that can exceed ~2 billionint64_t (or long long)plain int is 32 bits on today's desktops — and may be 16 elsewhere
bit manipulation, masks, flagsunsigned typesshifts and overflow are fully defined on unsigned
storing a pointer as an integeruintptr_tthe only sanctioned round-trip
everything else — small everyday numbersplain intthe natural word; don't reach for unsigned just because a value "can't be negative" — unsigned underflow bugs in loop conditions are legion
💡

The one-line habit: at any binary boundary say the width out loud (uint32_t); everywhere else keep it simple (int, size_t). And print fixed-width types only through their PRI… macros — a hardcoded %ld is a portability bug waiting for a Windows build.