The C Path — learn C, visuallyprintable cheatsheet

Escape sequences, literals & ASCII

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.

An escape sequence is two or more source characters that become one character in the compiled program: the \n you've typed since hello-world is backslash + n in your file, but a single byte (10) at runtime.

Simple escapes

escapebytenamenoteslearn
\n10newline (LF)ends a line; also flushes line-buffered stdout on terminals→ lesson
\t9horizontal tabquick-and-dirty columns→ lesson
\r13carriage returnreturn to column 0 — progress-bar trick \rdone: 42%; half of Windows' \r\n line ending
\\92backslasha literal \ — needed for Windows paths in literals
\'39single quoteneeded in char literals: '\''
\"34double quoteneeded in string literals: "she said \"hi\""
\?63question markexists only to defuse trigraphs (see below) — pointless since C23
\a7alert (bell)historically rang the terminal bell
\b8backspacemoves the cursor back one column
\f12form feedpage break on line printers
\v11vertical tabmuseum piece
\00NULreally the shortest octal escape — the string terminator→ lesson
\e27escape (ESC)not ISO C — a GNU extension. Portable: \x1b or \033 (start of ANSI color codes)

Anything else after a backslash (\q, \m…) is not a valid escape sequence — the standard gives it no meaning (compilers typically warn and guess).

Numeric escapes

formexampleruletrap
octal \o \oo \ooo'\033' = 27 (ESC)one to three octal digits; stops at the third digit or the first non-octal character"\0777" is two characters: \077 then '7'. And the value must fit in a byte — '\777' (511) is an error
hex \xh…'\x1b' = 27one or more hex digits — no length limitgreedy! "\x0aFF" tries to make one huge escape and fails to compile. Split the literal: "\x0a" "FF"
universal \uXXXX"\u00e9" = éexactly 4 hex digits naming a Unicode code point (C99)surrogates (D800–DFFF) and most points below A0 are forbidden
universal \UXXXXXXXX"\U0001F600" = 😀exactly 8 hex digits (C99)how it's encoded in a plain "…" depends on the execution charset — use u8"…" for guaranteed UTF-8
delimited C23"\x{1b}" "\o{33}" "\u{1F600}"braces end the greediness problemnew in C23 — older compilers reject

Character literals

literaltypenoteslearn
'a'intnot char!a C surprise (C++ differs): sizeof 'a' == sizeof(int). A char really is a small integer — 'A' is just 65→ lesson
'ab'intmulti-character constant: legal, value is ID — a portability trap, avoid
L'x'wchar_twide character — 32-bit on Linux, 16-bit on Windows (ID)
u'x'char16_tC11, from <uchar.h>
U'x'char32_tC11, from <uchar.h>
u8'x' C23char8_t (= unsigned char)C23 only; must be a single ASCII-range character→ lesson

Whether plain char is signed or unsigned is ID — signed on x86 Linux, unsigned on ARM Linux. Code that stores getchar()'s result in a char before comparing to EOF breaks either way: use int.

String literals

literalarray typenoteslearn
"hi"char[3]the terminator is included: sizeof "hi" is 3, strlen("hi") is 2→ lesson
L"hi"wchar_t[3]wide string — print with %ls
u8"hi"char[3] (C11) · char8_t[3] C23guaranteed UTF-8 encoding regardless of the compiler's charset→ lesson
u"hi" / U"hi"char16_t[3] / char32_t[3]C11; UTF-16/UTF-32 in practice (guaranteed in C23)
ruledetailslearn
modifying a literal is UBchar *s = "hi"; s[0] = 'H'; typically crashes (literals live in read-only memory). The array isn't const-typed in C — the compiler won't stop you. Prefer const char *s, or copy into char s[] = "hi";→ lesson
identical literals may be mergedwhether two "abc"s share one address is ID — never compare strings with ==→ lesson
adjacent literals concatenate"abc" "def" is one literal — how the PRId64 macros work, and how you split long lines. One prefixed + one plain: the prefix wins
backslash-newline continues a linea \ as the very last character splices the next source line on — works anywhere, not just strings (macros love it)
🪦

Trigraphs — 1989–2023, rest in peace. For keyboards lacking #[]{}|~^\, C89 made these nine sequences convert everywhere, even inside string literals: ??=# ??([ ??)] ??<{ ??>} ??/\ ??'^ ??!| ??-~. So "huh??!" silently became "huh|" — hence the \? escape. C23 removed trigraphs entirely; GCC only ever applies them in strict -std= modes (or with -trigraphs). Their cousins the digraphs (<% %> <: :> %:) are whole tokens, never touch strings, and are still legal.

ASCII 0–127

Every C string is bytes; these are the byte values (text-encoding lesson). Grey rows are control characters; the escape column shows the C spelling where one exists.

dechexcharesc
00x00NUL\0
10x01SOH
20x02STX
30x03ETX
40x04EOT
50x05ENQ
60x06ACK
70x07BEL\a
80x08BS\b
90x09HT\t
100x0ALF\n
110x0BVT\v
120x0CFF\f
130x0DCR\r
140x0ESO
150x0FSI
160x10DLE
170x11DC1
180x12DC2
190x13DC3
200x14DC4
210x15NAK
220x16SYN
230x17ETB
240x18CAN
250x19EM
260x1ASUB
270x1BESC\x1b
280x1CFS
290x1DGS
300x1ERS
310x1FUS
dechexcharesc
320x20SP
330x21!
340x22"\"
350x23#
360x24$
370x25%
380x26&
390x27'\'
400x28(
410x29)
420x2A*
430x2B+
440x2C,
450x2D-
460x2E.
470x2F/
480x300
490x311
500x322
510x333
520x344
530x355
540x366
550x377
560x388
570x399
580x3A:
590x3B;
600x3C<
610x3D=
620x3E>
630x3F?\?
dechexcharesc
640x40@
650x41A
660x42B
670x43C
680x44D
690x45E
700x46F
710x47G
720x48H
730x49I
740x4AJ
750x4BK
760x4CL
770x4DM
780x4EN
790x4FO
800x50P
810x51Q
820x52R
830x53S
840x54T
850x55U
860x56V
870x57W
880x58X
890x59Y
900x5AZ
910x5B[
920x5C\\\
930x5D]
940x5E^
950x5F_
dechexcharesc
960x60`
970x61a
980x62b
990x63c
1000x64d
1010x65e
1020x66f
1030x67g
1040x68h
1050x69i
1060x6Aj
1070x6Bk
1080x6Cl
1090x6Dm
1100x6En
1110x6Fo
1120x70p
1130x71q
1140x72r
1150x73s
1160x74t
1170x75u
1180x76v
1190x77w
1200x78x
1210x79y
1220x7Az
1230x7B{
1240x7C|
1250x7D}
1260x7E~
1270x7FDEL