Multi-File Projects & Makefiles
c1-1 named the gap directly — no official build tool. c1-5 introduced header files in passing. c2-4 explained exactly what #include does. This chapter puts all three together, for real, on a genuine multi-file project.
Why Split a Project Across Files
Organization at real scale, and something more practical: faster rebuilds. Recompiling one changed file is far cheaper than recompiling an entire project from scratch every time — but only if the build process actually takes advantage of that.
Header Files as Contracts
A header declares what a source file provides, without exposing how. Per c2-4's literal explanation, those declarations get pasted, verbatim, into every file that includes the header — giving each one enough information to call the functions without ever seeing their implementation.
Separate Compilation
This is c1-1's compile-then-link model, made concrete across multiple files. Each .c file compiles independently into its own object file (.o); only afterward are all the object files linked into one executable.
A Worked Example
The Linking Step, In Detail
main.o contains a call to add() with no body — an unresolved external symbol. math_utils.o contains add()'s actual definition. The linker's job is matching them up. Forget to link math_utils.o in at all, and the result is one of C's most recognizable real-world errors:
A Real Makefile
make is the unofficial-but-standard answer to c1-1's own open question — targets, dependencies, and rules, rebuilding only what actually changed.
Run make, and it rebuilds only the files whose dependencies (listed after the colon) are newer than their target — editing only math_utils.c recompiles just that file and re-links, leaving main.o untouched.
| Concept | Rust / cargo | C / make |
|---|---|---|
| Build tool | official, built in | unofficial — make/CMake fill the gap |
| Incremental rebuilds | automatic | explicit — dependency rules must be written correctly |
| Unresolved symbol | compile error — caught before linking exists as a concept | a distinct linker error — "undefined reference" |
make will report success while quietly building against outdated object files. Always list every header a .c file includes as part of its rule's dependencies.
Coding Challenges
Create the three files from this chapter's worked example (math_utils.h, math_utils.c, main.c), compile and link them manually with separate gcc -c and gcc linking commands, and run the result.
📄 View solutionDeliberately compile only main.c into an executable, omitting math_utils.o from the final link command. Report the exact error and explain what it means.
📄 View solutionWrite a Makefile for the three-file project from Challenge 1, following the chapter's own pattern. Explain what happens, in terms of which files actually get recompiled, if you run make twice in a row with no changes in between.
📄 View solutionChapter 5 Quick Reference
- A header declares what a source file provides — a contract, not an implementation
gcc -c file.c -o file.o— compiles independently into an object file- Linking matches unresolved calls (in one
.o) against real definitions (in another) - "undefined reference" — a linker error, distinct from a compile error; the definition was never linked in
make— the unofficial standard build tool, rebuilding only what its dependency rules say changed- Every header a source file includes must be listed as a dependency, or changes to it won't trigger a rebuild
- Next chapter: File I/O — fopen/fread/fwrite/fclose, text vs. binary mode