Video summary
Learn to program with c - Part 17 - Makefiles (and a little on multi-file projects)
Main summary
Key takeaways
Main ideas / concepts covered
-
Why split programs into multiple files
- As programs grow, it becomes important to modularize/compartmentalize code (e.g., put numerical routines into
numerical_operations.c). - Doing so requires a way to build an executable from multiple source files and dependencies.
- As programs grow, it becomes important to modularize/compartmentalize code (e.g., put numerical routines into
-
What “make” is for
- Make (specifically GNU Make / gnu make) automates building programs with dependency tracking.
- It determines what needs to be rebuilt by using a dependency graph:
- A main executable target depends on prerequisites (source files and/or other build products).
- If a prerequisite changed, the dependent target is rebuilt.
- If parts of the build tree didn’t change, they are not recompiled.
Methodology / structure of Makefiles (detailed)
1) Targets, prerequisites, and rules
- A basic rule has this conceptual form:
- Target:
default(or any file/name) - Prerequisites: a list of files/other targets, e.g.
hello - Rule (commands): commands run if the target is out of date
- Target:
- Example behavior described:
- Running
makeexecutes the first target in the Makefile. - A target is rebuilt if:
- The target file doesn’t exist, OR
- One or more prerequisites have changed since the last build.
- Running
2) Implicit rules (built-in behaviors)
- Make has implicit rules that automatically compile common file types:
- C source → executable
- If a target name matches a file and there exists a corresponding
.cfile (and no explicit rule exists), Make can build it implicitly.
- If a target name matches a file and there exists a corresponding
- C source → object file
- If a target is an object (e.g.,
funk.o) and there existsfunk.c, Make can implicitly run compilation with-c. - Example command conceptually: compile to
.owithout linking.
- If a target is an object (e.g.,
- C source → executable
- These implicit rules are used so you don’t always have to write explicit compilation commands for every intermediate step.
3) Dependency cascading (“tree” rebuilding)
- If
hellodepends onfunk.o, andfunk.cchanges:funk.obecomes out-of-date → rebuilt- then
hellois rebuilt
- If
funk.chasn’t changed,funk.oand everything depending on it may not need rebuilding.
4) The clean target
- A common non-default target is
clean:- Not the first target, so it won’t run on plain
make. - Runs commands like removing build artifacts (e.g.,
rm *.o, possibly removing executables too).
- Not the first target, so it won’t run on plain
5) Header files and correct dependencies
- Header files (
.h) contain function declarations (signatures), not implementations. - The implementation is in
.c; the header is included so other.cfiles compile without “implicit declaration” problems. - Makefile dependency example:
- If
funk.ois built fromfunk.c, andhello.cuses declarations fromfunk.h, - then Make should consider header changes:
funk.oshould depend on bothfunk.candfunk.hso that editing the header triggers recompilation.
- If
6) Scaling to multi-file / multi-module programs
- Typical structure:
- One
.cfile contains themainfunction (the executable entry point). - Other
.cfiles provide support functions (nomain). - Each support module often has:
- a
.cimplementation file (e.g.,a.c) - a
.hheader with declarations (e.g.,a.h)
- a
- One
- Linking step:
- The executable is produced by linking together object files (
*.o). - The module object files are passed to the compiler/linker along with the
mainobject file/source.
- The executable is produced by linking together object files (
7) Using variables to avoid repeating object lists
- Make variables can store lists of object files, e.g.:
objects = a.o b.o c.o d.o
- Then rules use the variable to build executables without repeating long file lists.
8) Automatic variables (to shorten rules)
- Two key automatic variables described:
$^: all prerequisites (space-separated)$@: the target name
- These help write generic-looking rules, especially when the prerequisite list is long or reused.
9) Nested / multi-level builds concept
- Demonstrates building a more complex executable where intermediate targets (object files and/or other compilation outputs) are built implicitly as dependencies.
- Key point:
- You often don’t need to explicitly compile every intermediate target; Make builds dependencies needed to satisfy the final link.
10) Pattern rules (custom implicit rules)
- Make can define a pattern rule using
%to match file stems. - Example described for:
- Building
.endfrom.text- rule uses:
- target pattern:
%.end - prerequisite pattern:
%.text
- target pattern:
- command transforms input by taking the last 10 lines (using
tail -n 10), then writes to the output target.
- rule uses:
- Building
- This shows how to automate transformations for arbitrary target types, not just compilation.
Practical workflow takeaways
- Split code into multiple
.cmodules with matching.hheaders. - Ensure Makefile dependencies reflect real usage:
- If headers change, rebuild affected object files.
- Rely on implicit rules for common compile steps (
.c → .o). - Use variables and automatic variables to keep Makefiles manageable.
- Use pattern rules for reusable file transformations.
Speakers / sources featured
- No specific named speakers or external sources are identified in the subtitles. (The content is presented as instruction by the video creator/instructor, but no name is given.)