The Language Processing System
Preprocessor, compiler, assembler, linker, loader — five tools, and the file each one hands to the next.
Skip to the animationTyping one compile command runs five separate programs in sequence — preprocessor, compiler, assembler, linker and loader — each consuming a specific file the previous one produced.
Five tools, five files
gcc hello.c looks like invoking a compiler. It is invoking a driver that runs a pipeline. Learning it as the files rather than the tools is what makes error messages readable.
| # | Tool | In | Out |
|---|---|---|---|
| 1 | Preprocessor | hello.c | hello.i — expanded source |
| 2 | Compiler | hello.i | hello.s — assembly text |
| 3 | Assembler | hello.s | hello.o — object file |
| 4 | Linker | hello.o + libraries | a.out — executable |
| 5 | Loader | a.out | a running process |
What each one actually does
- Preprocessor
- Pure text substitution — it does not understand C at all.
#includepastes in a whole file,#definesubstitutes macros,#ifdefkeeps or deletes branches. This is why a macro can produce an error inside a header you never opened: the compiler only ever sees the result. - Compiler
- The entire rest of this subject lives here: lexical analysis, parsing, semantic analysis, intermediate code, optimisation, code generation. Its product is assembly source — readable mnemonics, not binary.
- Assembler
- Assembly maps almost one-to-one onto machine instructions, so this is a lookup rather than a translation. It emits an object file: real machine code, plus a relocation table naming every symbol it could not resolve.
- Linker
- Collects every object file and library, builds one table of which symbol is defined where, and patches each unresolved reference with a real address.
- Loader
- Belongs to the operating system. Allocates an address space, copies code and data in, sets up stack and heap, resolves dynamic libraries and jumps to the entry point.
Why an object file has holes in it
When the assembler reaches call printf, it has never seen printf — it is in a library compiled long ago. It cannot invent the address, so it emits the instruction with the operand left blank and records an entry in the relocation table saying "whoever links this, put the address of printf here".
This is the whole explanation of undefined reference to 'printf'. Compilation succeeded; the linker simply found nothing defining that name. It is never a syntax problem, and adding a header will not fix it — a header declares, a library defines.
Static versus dynamic linking
| Static (.a, .lib) | Dynamic (.so, .dll) | |
|---|---|---|
| Library code | copied into the executable | referenced by name |
| Executable size | larger | smaller |
| Resolved | at link time | at load time, every run |
| Library security fix | requires a rebuild | applies system-wide immediately |
| Runs on a machine without the library | yes | no — startup fails |
| Memory with many processes | one copy each | one copy shared |
"Cannot open shared object file" and "missing DLL" are the loader failing at step 5, on a program that built and linked perfectly. The distinction above is the entire explanation.
Reading errors by which file they mention
- Something expanded strangely, or an error inside a system header → preprocessor, look at your macros.
- Syntax error, type error, undeclared variable → compiler.
undefined reference/unresolved external symbol→ linker, you are missing a library or a definition.multiple definition→ linker, the same symbol defined in two object files.- Builds fine, fails to start with a missing library → loader, dynamic linking.
Watch it work
Check yourself
question 1 / 4
One question at a time. Pick an answer to see why it is right or wrong, then move on — there is no score to keep and nothing is saved.