The C Path — learn C, visuallyprintable cheatsheet

GCC flags worth knowing by heart

GCC accepts hundreds of options; these are the ~30 that matter. Nearly all work identically in Clang.

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

💡

Your everyday invocation — make it muscle memory (or a shell alias):
gcc -std=c17 -Wall -Wextra -Wshadow -g -Og program.c -o program

Warnings: free code review, on by request

By default GCC is scandalously quiet. Fix that — always:

flagwhat it doeslearn
-Wallthe "sensible warnings" set — badly named: it's nowhere near all. Includes the format-string checks that catch printf mismatches→ lesson
-Wextramore good ones: unused parameters, signed/unsigned comparisons…
-Wpedanticcomplain about anything that isn't strictly ISO C (pair with an explicit -std=)
-Werrorpromote warnings to errors so nobody can ignore them. Great for your own code; think twice before forcing it on code you distribute — new compiler versions add new warnings and break the build

The extras worth adding:

flagcatches
-Wshadowan inner variable hides an outer one — a silent logic-bug generator
-Wconversionimplicit conversions that can lose data (int rounded = celsius; truncating a double)
-Wsign-conversionsilent signed↔unsigned changes — the -1 > 1u family of bugs
-Wdouble-promotionfloat silently computed in double — matters for embedded/hot loops
-Wformat=2stricter printf/scanf checking than -Wall, plus warns when the format string isn't a literal (security)
-Wundef#if MISPELLED_MACRO silently evaluating as 0
-Wvlavariable-length arrays — stack overflows in disguise on untrusted sizes
-Wwrite-stringsassigning a string literal to char * — modifying one is UB
-Wnull-dereferencepaths that provably dereference NULL
-Wstrict-prototypesold-style int f() declarations that accept any arguments (moot in C23, where () finally means (void))

Which C? -std=

flagmeaninglearn
-std=c17portable ISO C17 — today's safe default→ lesson
-std=c23ISO C23 (GCC 14+; on older GCC the working name was -std=c2x)→ lesson
-std=gnu17C17 + GNU extensions — GCC's own default dialect on most installs (GCC 15 moved to gnu23)→ lesson
-ansimuseum piece: means -std=c90 — you'll meet it in old build scripts

Optimization: the speed dial

flagmeaningtrade-off
-O0default — no optimizationfast compiles, faithful line-by-line debugging, slow code
-Ogoptimize, but keep debuggablethe sweet spot for development builds
-O1basic optimizationsrarely used directly
-O2full optimization, no size explosionthe production standard
-O3-O2 + aggressive inlining/vectorizationsometimes faster, sometimes bigger & slower — measure!
-Osoptimize for sizeembedded targets, cache-bound code
-march=nativeuse every instruction this CPU supports (AVX2, FMA…)great for code that runs where it's built; wrong for binaries you ship to older machines
-fltolink-time optimization — optimize across .c file boundariesslower builds, real speedups; pass it to both compile and link steps
-ffast-mathlet the optimizer break IEEE float rulesfaster, wrong for code that cares about NaN/precision — know what you're signing

Debug info & sanitizers

flagwhat it doeslearn
-gembed DWARF debug info mapping machine code back to source lines and variable names. Does not slow the program down — it just makes the file bigger. There is never a reason to omit it during development; ship with it too (you can strip symbols later, but you can't conjure them back when a core dump lands)→ lesson
-g3-g plus macro definitions — lets gdb expand your #defines
-fsanitize=addressAddressSanitizer (ASan): catches buffer overflows, use-after-free, stack overflows at the moment they happen; leak check runs at exit. ~2× slowdown — dev/test builds only→ lesson
-fsanitize=undefinedUBSan: catches signed overflow, bad shifts, null dereference, misaligned access as they execute→ lesson
-fsanitize=address,undefinedthe combo for dev builds — many teams gate every merge on it
-fsanitize=threadTSan: data races. Cannot be combined with ASan; don't mix sanitized binaries with valgrind either
-fno-omit-frame-pointerkeeps stack traces (from ASan, perf, gdb) accurate at small cost

The plumbing

flagdoesexample / notelearn
-o filename the outputgcc app.c -o app — default is the immortal a.out
-ccompile to a .o object, don't linkthe heart of every Makefile's incremental build→ lesson
-E / -Sstop after preprocessing / after compiling to assemblysee what the preprocessor or optimizer actually did→ lesson
-DNAME[=val]define a macro from the command line-DDEBUG -DVERSION=3→ lesson
-Idiradd a directory to the #include search path-Iinclude/ — used by the preprocessor→ lesson
-Ldiradd a directory to the library search path-Lbuild/lib — used by the linker→ lesson
-lnamelink against libname-lm → libm, the math library. Order matters: put -l flags after your .c/.o files→ lesson
-fPIC -sharedbuild a shared librarygcc -fPIC -shared mylib.c -o libmylib.so→ lesson
-staticlink everything statically — self-contained binarybigger file, no runtime .so dependencies→ lesson

Hardening basics (release builds)

flagprotects againstnote
-D_FORTIFY_SOURCE=2adds runtime checks to memcpy/sprintf/… when sizes are knownneeds optimization on (-O1+); =3 checks more (GCC 12+, glibc 2.34+)
-fstack-protector-strongstack-smashing canaries on vulnerable functionscheap; distro default in many places
-fPIE -pieposition-independent executable → full ASLRcompile flag + link flag; default on most modern distros
-Wl,-z,relro -Wl,-z,nowmakes the relocation/GOT tables read-only after startup"full RELRO" — linker flags, hence the -Wl, prefix
-fstack-clash-protectionstack-clash attacks via huge stack allocationscheap insurance
-fcf-protectionROP/JOP control-flow hijacks (x86 CET)hardware-assisted on recent CPUs
-ftrivial-auto-var-init=zeroreading uninitialized locals becomes reading zerosGCC 12+; defense-in-depth, not a license to skip initialization — the read is still a bug (→ lesson)

Put it together

Development — warnings, debuggability, sanitizers:

gcc -std=c17 -Wall -Wextra -Wshadow -Wconversion \ -g -Og -fsanitize=address,undefined \ program.c -o program

Release — optimized, hardened, still debuggable from a core dump:

gcc -std=c17 -Wall -Wextra -O2 -g \ -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fPIE -pie \ -Wl,-z,relro -Wl,-z,now \ program.c -o program -lm
🎉

Clang compatibility: nearly every flag on this page works identically in Clang — clang -std=c17 -Wall -Wextra -O2 just works. The two compilers deliberately share a command-line dialect, so learning one is learning both.