Bringing Friends to the Party
Almost every non-trivial program depends on external code — the C standard library for printf, the math library for sin, OpenSSL for TLS. The question is how that external code gets incorporated into your program. There are two strategies, with fundamentally different trade-offs.
Static Linking
The linker copies the machine code from each required library directly into your executable. The result is a single, self-contained binary with no external dependencies. The size difference is dramatic in practice — a small program that links dynamically against libc might be barely over 10 KB, while the same program statically linked can balloon past 800 KB, because it now carries a full copy of everything it uses from the C library.
Pros:
- Runs on any compatible system, even if libraries are not installed.
- No version mismatch issues ("dependency hell").
- Slightly faster startup (no loader resolution step).
- Ideal for containers, embedded systems, and Go binaries (Go statically links by default).
Cons:
- Large binary size — every program carries its own copy of every library.
- Security updates require recompiling every binary. If libc has a critical CVE, patching the system library does not fix statically linked programs.
- Higher RAM usage — no sharing of library code between processes.
Dynamic Linking
The linker records only a reference to the library — its name and the symbols needed. The actual library code is loaded at runtime by the dynamic loader, which resolves those references against the shared libraries present on the system.
Pros:
- Small binary size.
- Memory efficient: the kernel maps one physical copy of libc.so into the address space of every process that uses it (using copy-on-write pages).
- Security patches are instant: update libc.so once, all programs benefit immediately.
Cons:
- Requires the correct library version to be present at runtime.
- Slightly slower startup (loader must resolve symbols).
- "Dependency hell": different programs may require incompatible versions of the same library.
The Tool: ldd
ldd (List Dynamic Dependencies) shows every shared library a binary requires and where the loader will find it — printing each dependency as a name, its resolved path on disk, and the address it was loaded at. If a library shows not found instead of a path, the program will fail to start because the loader can't locate a dependency it needs.
Security note: Never run ldd on an untrusted binary. ldd works by setting an environment variable that causes the binary to print its dependencies — which means it executes the binary in a special mode. Inspecting a binary's NEEDED entries with objdump instead is the safe alternative, since it reads the ELF metadata without executing anything.