What Is a Linker?

A linker is a specialized tool that takes one or more object files (produced by compilers or assemblers) and combines them into a single executable program.

Think of a linker as the final assembler of a jigsaw puzzle — it connects all pieces (functions, variables, libraries) into a coherent whole.

Linkers are essential in:

  • Multi-file projects
  • Modular development
  • Library integration
  • System-level programming

1. Compilation and Linking Workflow

Here’s how a typical C program becomes an executable:

main.c → [Compiler] → main.o
utils.c → [Compiler] → utils.o

[Linker] → main.o + utils.o + stdlib → main.exe

In many languages (like C or C++), compilation and linking are distinct phases.

2. Role of the Linker

The linker is responsible for:

TaskDescription
Symbol ResolutionMaps function/variable names to memory addresses
RelocationAdjusts addresses of code/data to fit final memory layout
Library LinkingIntegrates standard or user-defined libraries
Entry Point SetupDefines where the program starts (main in C/C++)
Executable Output GenerationProduces .exe, .out, or .elf binaries

3. Types of Linkers

TypeDescription
Static LinkerMerges all code into one final executable (no external dependencies)
Dynamic LinkerLeaves external references unresolved and binds them at runtime
Incremental LinkerRe-links only modified parts of large projects
Link-Time Optimizer (LTO)Performs whole-program optimization during linking

4. Static Linking

In static linking:

  • All required code (from object files and libraries) is copied into the final binary.
  • Produces self-contained executables.

Pros:

  • No runtime dependencies
  • Faster startup

Cons:

  • Larger file sizes
  • Harder to update (must recompile to patch)

5. Dynamic Linking

In dynamic linking:

  • External functions are not embedded, but referenced in external shared libraries (e.g., .dll, .so, .dylib)
  • Linking occurs at runtime

Pros:

  • Smaller binaries
  • Shared memory between programs
  • Easier updates (just replace the shared library)

Cons:

  • Requires external files at runtime
  • Slightly slower startup

6. Symbol Resolution: How Names Become Addresses

During linking, symbolic names (like printf, main, global_var) are resolved into memory addresses.

Example:

In main.o:

extern int square(int);

In math.o:

int square(int x) {
    return x * x;
}

The linker:

  • Finds square in math.o
  • Replaces the placeholder in main.o with the correct address
  • Ensures both modules reference the same function

7. Relocation: Adjusting for Final Memory Layout

Each object file assumes it starts at address 0, which obviously isn’t possible in a multi-module program.

The linker:

  • Moves functions/data to proper addresses in memory
  • Updates all internal address references

This relocation step ensures your function calls and memory access work even when addresses change.

8. Linker Script

In low-level or embedded systems, developers may provide a linker script to control:

  • Code and data placement
  • Stack and heap configuration
  • Memory segments and alignment

Example (GNU LD):

SECTIONS {
    .text : { *(.text) }
    .data : { *(.data) }
    .bss  : { *(.bss)  }
}

This level of control is crucial in firmware, operating systems, and real-time systems.

9. Link-Time Optimization (LTO)

Modern compilers (like GCC and Clang) support LTO, which allows the compiler to defer optimization to the linker stage.

Advantages:

  • Inlining across modules
  • Dead code elimination at global scope
  • Better call graph analysis

This is crucial for performance-critical software like:

  • Operating systems
  • Games
  • Financial systems

10. Errors During Linking

Linking is a common stage where many build errors occur.

Error TypeExampleCause
Undefined referenceundefined reference to 'foo'Missing object file or library
Multiple definitionsmultiple definition of 'main'Same symbol defined in multiple files
Mismatched typesconflicting types for 'bar'Function declarations don’t match

Fixing these often requires inspecting:

  • Compiler flags (-c, -Wall, etc.)
  • Linker inputs (-lmath, -lm, etc.)
  • File inclusion order

11. Common Linkers and Tools

Linker ToolPlatformNotes
GNU ldUnix/LinuxMost widely used linker
GoldGoogle’s faster replacement for GNU ld
LLDLLVM’s modern linker (cross-platform)
MSVC LinkerMicrosoft toolchain for Windows
ld.bfdGNU binutils default linker

12. Linker Flags and Options

Typical compiler commands invoke the linker implicitly:

gcc main.o utils.o -o program -lm
  • -o program: Output executable name
  • -lm: Link the math library (libm.so or libm.a)
  • -L: Add library search path
  • -static: Force static linking
  • -shared: Create a shared object

13. Real-World Use Case: Combining C and Assembly

; file: asm_func.asm
global my_asm_add
my_asm_add:
    add eax, ebx
    ret
// file: main.c
extern int my_asm_add(int, int);
int main() {
    return my_asm_add(5, 7);
}

Build process:

nasm -f elf32 asm_func.asm
gcc -m32 main.c asm_func.o -o program

The linker combines C and assembly via symbol resolution and relocation.

Summary

A linker is the unsung hero of software development — silently stitching together code fragments, libraries, and binaries into fully-formed executables. It ensures that all the functions you write (or borrow) are properly located, aligned, and callable.

Without a linker, your source code is just a pile of puzzle pieces — the linker completes the picture.

Related Keywords

  • Object File
  • Compiler
  • Assembler
  • Static Linking
  • Dynamic Linking
  • Shared Library
  • Relocation
  • Symbol Table
  • Undefined Reference
  • Linker Script
  • Executable File
  • Binary Format (ELF, PE, Mach-O)
  • LTO (Link-Time Optimization)
  • GNU ld
  • GCC
  • Cross-Linking
  • Memory Segments
  • Address Resolution
  • Build System