C cheat sheet
A scannable C reference: 19 short snippets across 10 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Syntax and the compiled toolchain | C is compiled ahead of time. A single gcc hello.c -o hello hides four stages; running them separately is the fastest | lesson |
| Pointers and memory | A pointer holds an address. The two operators are & (address of) and * (dereference, the value at that address) | lesson |
| Structs, files and undefined behaviour | The standard defines some operations as undefined behaviour: the compiler may assume they never occur and optimise | lesson |
| Setting up a C toolchain: compiler, make and gdb | C has no package manager of its own. What you install is a toolchain: a compiler driver (gcc or clang), an assembler, a | lesson |
| Arrays, strings and the string library | In almost every expression an array name decays to a pointer to its first element. That is why sizeof inside a function | lesson |
| Dynamic memory patterns and allocation bugs | C has no destructors, so ownership is a convention you write down. Pick one rule per pointer and stick to it: the | lesson |
| Preprocessor, headers and multi-file projects | A header declares what other files may use; a .c file defines it. Every header needs an include guard, and anything not | lesson |
| Building and linking: static and shared libraries | Compiling (-c) and linking are separate steps. A link error means the compiler was satisfied but the linker could not | lesson |
| Debugging with gdb, sanitizers and valgrind | A watchpoint is the fastest way to find who corrupts a variable: set it on the field that goes wrong and gdb stops at | lesson |
| Portability, standards and disciplined C | The standard does not fix the size of int or long. It fixes minimum ranges, so int is 16, 32 or 64 bits depending on | lesson |
Quick snippets
Syntax and the compiled toolchain
From source to executable
gcc -E hello.c -o hello.i # preprocess: #include and #define are expanded
gcc -S hello.i -o hello.s # compile: C becomes assembly
gcc -c hello.s -o hello.o # assemble: assembly becomes an object file
gcc hello.o -o hello # link: objects plus libraries become an executable
# in practice, one command, with the warnings turned up
gcc -std=c17 -Wall -Wextra -Wpedantic -g -O2 hello.c -o hello
Reading compiler and linker output
$ gcc -Wall -Wextra -c demo.c
demo.c: In function 'main':
demo.c:12:13: warning: format '%d' expects argument of type 'int', but argument 2
has type 'const char *' [-Wformat=]
demo.c:20:5: error: 'count' undeclared (first use in this function)
# linker errors name symbols, not lines
$ gcc main.o util.o -o app
/usr/bin/ld: main.o: in function 'main':
main.c:(.text+0x2a): undefined reference to 'helper'
collect2: error: ld returned 1 exit statusFull lesson: Syntax and the compiled toolchain →
Pointers and memory
Allocating and freeing
#include <stdlib.h>
#include <string.h>
size_t n = 1000;
int *buf = malloc(n * sizeof *buf); /* sizeof *buf, not sizeof(int) */
if (buf == NULL) {
return EXIT_FAILURE; /* allocation failed: nothing to free */
}
memset(buf, 0, n * sizeof *buf);
/* ... use buf ... */
free(buf);
buf = NULL; /* makes a later double free harmless */
Allocating and freeing
/* the realloc trap: assigning straight back loses the old block on failure */
int *grown = realloc(buf, 2000 * sizeof *buf);
if (grown == NULL) {
free(buf); /* buf is still valid and still yours to free */
return EXIT_FAILURE;
}
buf = grown;
Strings and buffers
char dst[8];
strcpy(dst, "123456789"); /* writes 10 bytes into 8: undefined behaviour */
strncpy(dst, src, sizeof dst - 1); /* bounded, but may leave no terminator */
dst[sizeof dst - 1] = '\0'; /* so terminate it yourself */
snprintf(dst, sizeof dst, "%s-%d", src, 7); /* truncates safely, always terminates */
/* strlen walks bytes until the terminator, so it needs a terminated buffer */
size_t len = strlen(dst);Full lesson: Pointers and memory →
Structs, files and undefined behaviour
Structs
/* returning a struct by value copies it, so the result is safe to use */
struct point midpoint(struct point a, struct point b) {
struct point m = { (a.x + b.x) / 2.0, (a.y + b.y) / 2.0 };
return m;
}
/* structs are compared member by member; == does not compile for them */
if (r1.id == r2.id && strcmp(r1.name, r2.name) == 0) { /* equal */ }
Undefined behaviour
# make these visible instead of silent
gcc -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer demo.c -o demo
./demo
# AddressSanitizer: heap-buffer-overflow on address 0x602000000014
# runtime error: signed integer overflow: 2147483647 + 1 cannot be representedFull lesson: Structs, files and undefined behaviour →
Setting up a C toolchain: compiler, make and gdb
Getting a compiler and choosing a standard
# check what you actually have
cc --version
gcc --version
clang --version
make --version
gdb --version
# Debian and Ubuntu
sudo apt install build-essential gdb valgrind
# macOS: clang arrives with the command line tools
xcode-select --install
Getting a compiler and choosing a standard
gcc -std=c17 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Werror -g -O0 main.c -o app
Running under gdb
gcc -std=c17 -Wall -Wextra -g -O0 main.c -o app
gdb ./app
# inside gdb
(gdb) break main
(gdb) run
(gdb) next # step over
(gdb) step # step into
(gdb) print i
(gdb) backtrace
(gdb) continue
(gdb) quitFull lesson: Setting up a C toolchain: compiler, make and gdb →
Arrays, strings and the string library
Strings and their pitfalls
char a[] = "hello"; /* 6 bytes, mutable, writable copy */
char *b = "hello"; /* pointer to read-only literal: do not modify */
const char *c = "hello"; /* the honest declaration */
/* b[0] = 'H'; undefined behaviour, often a segfault */Full lesson: Arrays, strings and the string library →
Dynamic memory patterns and allocation bugs
The four bugs valgrind finds
# sanitizers first: fast and precise
cc -std=c17 -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer app.c -o app
./app
# valgrind when you cannot recompile, or want leak accounting
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./app
valgrind --tool=helgrind ./threaded_app # race detectionFull lesson: Dynamic memory patterns and allocation bugs →
Preprocessor, headers and multi-file projects
Macros and their traps
/* prefer these */
enum { MAX_ITEMS = 16 }; /* a real type-checked constant */
static const double PI = 3.14159265358979;
static inline int square(int x) { return x * x; }
/* use a macro only when you need the type or the caller's scope */
#define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0]) /* arrays only, not pointers */
#define MAX(a, b) ((a) > (b) ? (a) : (b)) /* arguments parenthesised */
Macros and their traps
#define LOG(fmt, ...) fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define CHECK(cond, ...) do { if (!(cond)) { LOG("check failed: " #cond, __VA_ARGS__); } } while (0)Full lesson: Preprocessor, headers and multi-file projects →
Building and linking: static and shared libraries
Object files and symbols
cc -std=c17 -Wall -Wextra -c mathx.c -o mathx.o
nm mathx.o # T = defined text, U = undefined, D = data
nm -C mathx.o # demangle C++ names
objdump -d mathx.o # disassembly
readelf -h mathx.o # ELF header, architecture and type
# why a symbol is missing
nm -u main.o # everything main.o still needs
pkg-config and diagnosing link failures
pkg-config --cflags --libs libcurl # -> -I... -lcurl
cc main.c $(pkg-config --cflags --libs libcurl) -o app
# typical failures and their real meaning
# undefined reference to 'sqrt' -> add -lm
# cannot find -lmylib -> -L path wrong or library not built
# relocation R_X86_64_PC32 ... recompile with -fPIC
# duplicate symbol -> two strong definitions, often a header definition
# version GLIBC_2.34 not found -> binary built on a newer distributionFull lesson: Building and linking: static and shared libraries →
Debugging with gdb, sanitizers and valgrind
Core dumps and post-mortem debugging
ulimit -c unlimited # allow core files
cat /proc/sys/kernel/core_pattern # where they land
gdb ./app core
(gdb) backtrace
(gdb) frame 0
(gdb) info registers
(gdb) print *(struct Conn *)argFull lesson: Debugging with gdb, sanitizers and valgrind →
Portability, standards and disciplined C
Undefined, unspecified and implementation-defined
/* all of these are undefined behaviour */
int x = INT_MAX + 1; /* signed overflow */
int a[4]; a[4] = 0; /* one past the end */
p = malloc(8); free(p); *p = 1; /* use after free */
i = i++ + 1; /* two unsequenced modifications */
shift = 1 << 32; /* shift >= the width of the type */
memcpy(dst, dst + 1, 10); /* overlapping regions */
/* and these are merely unspecified, not UB */
printf("%d %d\n", f(), g()); /* argument evaluation order is unspecified */
Making the compiler your reviewer
CFLAGS := -std=c17 -O2 -g \
-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion \
-Wshadow -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes \
-Wformat=2 -Wundef -Wvla -Wwrite-strings -Werror
# extra hardening for shipped binaries
cc $(CFLAGS) -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE -pie app.c -o app
# static analysis
clang --analyze app.c
cppcheck --enable=all --inconclusive app.cFull lesson: Portability, standards and disciplined C →
FAQ
Is this C cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
Last refreshed 2026-09-27.