C Basics · beginner · ~15 min

Makefiles — the C build system

Read and write small Makefiles; understand dependency-driven rebuilds.

Overview

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.

Why it matters

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.

Core concepts

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.

Syntax notes

# Variables
CC := gcc
CFLAGS := -Wall
# Rule
target: prereqs
	recipe         # MUST be a tab, not spaces

Lesson

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.

Code examples

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

Line by line

# 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)

Common mistakes

  • Using spaces instead of tabs for recipe indentation.
  • Forgetting -c on the per-.o rule (then you'd link too early).

Debugging tips

make -n prints the recipes without running them. make -p dumps the full database (variables, rules) Make is using.

Memory safety

Make doesn't touch memory; this lesson is about build hygiene.

Real-world uses

Every C project ever shipped. Linux kernel, glibc, OpenSSL, sqlite, every embedded RTOS.

Practice tasks

  1. Write a Makefile that builds a single-file program. 2. Extend to two source files. 3. Add a clean target.

Summary

Targets, prereqs, recipes. TAB not spaces. $@ $< $^ are your friends. Set warning + sanitiser flags in your dev variant.