A topic in the Open Knowledge Graph — a free, open map of 15,495 topics and the order to learn them in.
A compiler translates each source file separately into a relocatable object file containing machine code, data and a symbol table, but with some addresses left unfilled. The linker combines object files and libraries into one executable by doing two jobs: symbol resolution, which matches every reference to a function or global variable with exactly one definition, and relocation, which assigns final addresses and patches every reference to use them. The loader then copies the executable into memory and starts it. Shared libraries move part of the linking to load time or run time, so many programs can share one copy of common code.
Compile two small C files with `gcc -c`, inspect the object files with tools such as `nm` and `objdump` to see undefined symbols and relocation entries, and then link them. Trigger the two classic link errors on purpose: an undefined reference and a duplicate definition. Finally, build the same program statically and dynamically and compare file sizes and the output of `ldd`, following the linking chapter of Bryant and O'Hallaron's *Computer Systems: A Programmer's Perspective*.
Real programs are split across many source files, and they call code nobody in the project wrote, such as `printf`. The compiler handles one file at a time. When it compiles `main.c` and sees a call to `compute()` defined in `util.c`, it cannot know where `compute` will sit in memory. So it emits a relocatable object file (on Linux, an ELF `.o` file). That file holds machine code in a `.text` section, initialized globals in `.data`, a symbol table listing what the file defines and what it uses, and relocation entries marking every spot that needs an address filled in later.
The linker, such as `ld`, which `gcc` runs for you, turns a set of object files into an executable in two steps. Symbol resolution matches each reference to exactly one definition. If no definition exists anywhere, you get "undefined reference". If two files define the same global function, you get a "multiple definition" error. Relocation then merges the sections of all files, for example every `.text` into one code section. It assigns each section and symbol its final address and walks the relocation entries, patching each call and each global access with the right address.
Libraries come in two forms. A static library (a `.a` archive on Linux) is a bundle of object files. The linker copies in only the members that resolve some needed symbol, and the result is a self-contained executable. A shared library (`.so` on Linux, `.dll` on Windows) is linked differently. At build time the linker records only that the program needs, say, `libc.so.6`. When the program starts, a dynamic linker maps the library into the process and connects the program's references to it. Shared libraries are compiled as position-independent code, so the same physical pages can be mapped at different addresses in different processes.
The loader is the last step. When a shell runs a program, the `execve` system call reads the executable's header and maps its code and data segments into a fresh address space. It sets up the stack with the arguments and environment, and on systems that use shared libraries it hands control to the dynamic linker. Execution finally reaches the program's entry point, typically a startup routine named `_start`, which in turn calls `main`.
Knowing these stages turns confusing build failures into ordinary diagnostics. A compile error points at a line of source. A link error points at a missing or duplicated symbol. A load-time error, such as a missing shared library, points at the environment the program runs in.
No topics depend on this one yet.