separate compilation
Imagine a program made of fifty source files, and you fix a typo in just one of them. Must the whole thing be rebuilt from scratch every time? Separate compilation is the answer that says no: each source file is compiled on its own into its own object file, and only the files that actually changed need to be recompiled. The leftover object files from before are reused, and a final quick link step ties everything together. It is the difference between reprinting one page of a book versus the entire book.
The way it works rests on the translation unit and the declaration/definition split. Each .c file is turned into a .o file in isolation, knowing only the declarations it was given through headers, not the full code of the other files. When file A calls a function defined in file B, A is compiled against just B's declaration (a promise from a header) and leaves the actual address as an undefined symbol; the linker fills that in afterward. So the compiler never needs to see all the source at once, and any file can be compiled independently and in any order, even in parallel across many processor cores.
This is why C and C++ projects are structured around headers and source files, and why build tools track which files changed so they can rebuild the minimum needed. The payoff is speed on large codebases: editing one file means recompiling one file, not millions of lines. The cost is the discipline it demands, the need to keep headers and source files in agreement, and the linker errors that appear when they disagree (a function declared but never defined, or defined twice). Separate compilation is the reason linking exists as its own stage at all.
$ gcc -c main.c # -> main.o (only this file recompiled after an edit) $ gcc -c utils.c # -> utils.o (unchanged: reuse the old .o, skip recompiling) $ gcc main.o utils.o -o app # quick final link of the object files
Change one file, recompile only it, reuse the rest, then relink: that is separate compilation.
Separate compilation means the compiler never sees the whole program at once, so cross-file mistakes (a missing or duplicate definition) cannot be caught until the linker runs.