C Basics · beginner · ~15 min
Read and write small Makefiles; understand dependency-driven rebuilds.
Make turns 'build the program' into 'rebuild exactly what changed'. For C projects, this means each translation unit compiles independently and only re-links when an object changes.
Every C codebase, from kernels to embedded firmware, builds with Make or a Make-equivalent (CMake → Make, Ninja). Reading a Makefile is part of reading any C project.
Rule shape. target: prereqs\n<TAB>recipe. The tab is mandatory; spaces fail mysteriously.
Pattern rules. %.o: %.c\n<TAB>$(CC) -c -o $@ $<. $@ is the target, $< is the first prereq, $^ is all prereqs.
Phony targets. clean, all, test — not files. Mark them with .PHONY: clean test.
Pentester mindset. Read the build system before you read the source — it tells you what compile flags hardening flags are on. Missing -fstack-protector-strong or -D_FORTIFY_SOURCE=2 is a red flag.
Defensive coding habit. Always set CFLAGS = -Wall -Wextra -Wpedantic -fsanitize=address,undefined in your dev Makefile. Add production hardening flags in the release variant.
# Variables
CC := gcc
CFLAGS := -Wall
# Rule
target: prereqs
recipe # MUST be a tab, not spaces
Make encodes rules: target -> prerequisites -> recipe. When prerequisites are newer than the target, Make runs the recipe. For C, the common pattern is: each .o depends on its .c; the final binary depends on every .o.
CC := gcc
CFLAGS := -Wall -Wextra -O2 -g
prog: main.o util.o
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f *.o prog
# Build prog from main.o + util.o
prog: main.o util.o
$(CC) $(CFLAGS) -o $@ $^ # $@ = prog, $^ = main.o util.o
# Pattern rule: any .o from its .c
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $< # $< = first prereq (the .c)
-c on the per-.o rule (then you'd link too early).make -n prints the recipes without running them. make -p dumps the full database (variables, rules) Make is using.
Make doesn't touch memory; this lesson is about build hygiene.
Every C project ever shipped. Linux kernel, glibc, OpenSSL, sqlite, every embedded RTOS.
clean target.Targets, prereqs, recipes. TAB not spaces. $@ $< $^ are your friends. Set warning + sanitiser flags in your dev variant.