Skip to main content

Static vs Dynamic Linking

Linking can be static (library code copied into executable) or dynamic (library loaded at runtime). Each has trade-offs in size, deployment, and performance.

Key Difference

Static: Self-contained executable, larger size
Dynamic: Smaller executable, requires library files at runtime

Comparison Overview

AspectStatic (.a, .lib)Dynamic (.so, .dll, .dylib)
SizeLarge (includes library)Small (references library)
DependenciesNone (self-contained)Requires library files
UpdatesRecompile neededLibrary updates affect all
Load TimeFasterSlower (runtime loading)
MemoryDuplicated per processShared between processes
DeploymentEasy (single file)Complex (multiple files)

Static Linking

Library code is copied into the executable at link time.

Creating Static Library

# Compile to object files
g++ -c utils.cpp -o utils.o
g++ -c math.cpp -o math.o

# Create static library (archive)
ar rcs libmylib.a utils.o math.o

# Verify contents
ar t libmylib.a
# Output:
# utils.o
# math.o

The ar tool bundles object files into an archive (.a on Unix, .lib on Windows).

Linking Statically

# Link with static library
g++ main.cpp -o app -L. -lmylib

# Or explicitly
g++ main.cpp libmylib.a -o app

# Force static linking of all libraries
g++ main.cpp -o app -static -lmylib

The executable contains all code from libmylib.a:

ls -lh app
# app: 2.5 MB (includes mylib code)

Pros of Static Linking

No runtime dependencies - works anywhere
Faster startup - no loading needed
Versioning control - exact library version embedded
Easier deployment - single file

Cons of Static Linking

Large executable size - includes all library code
No updates - must recompile to update library
Memory duplication - each process has own copy
Longer link time - more code to process


Dynamic Linking

Library code is loaded at runtime from shared library files.

Creating Shared Library

# Compile with position-independent code (-fPIC)
g++ -fPIC -c utils.cpp -o utils.o
g++ -fPIC -c math.cpp -o math.o

# Create shared library
g++ -shared utils.o math.o -o libmylib.so # Linux
g++ -dynamiclib utils.o math.o -o libmylib.dylib # macOS
cl /LD utils.obj math.obj /OUT:mylib.dll # Windows

-fPIC (Position-Independent Code) allows the library to load at any memory address.

Linking Dynamically

# Link with shared library
g++ main.cpp -o app -L. -lmylib

# Executable is smaller
ls -lh app
# app: 50 KB (doesn't include mylib code)

Runtime Loading

The executable records which libraries it needs:

# Linux: Show dynamic dependencies
ldd app
# Output:
# libmylib.so => /usr/lib/libmylib.so
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6

# macOS
otool -L app

# Windows
dumpbin /DEPENDENTS app.exe

At runtime, the dynamic linker (ld.so on Linux) loads these libraries into memory.

Library Search Paths

The loader searches:

  1. Directories in executable's RPATH (embedded path)
  2. LD_LIBRARY_PATH environment variable
  3. System paths (/usr/lib, /lib)
# Set runtime search path during linking
g++ main.cpp -o app -L. -lmylib -Wl,-rpath,/path/to/libs

# Or set at runtime
export LD_LIBRARY_PATH=/path/to/libs:$LD_LIBRARY_PATH
./app

Pros of Dynamic Linking

Small executable - library code separate
Shared memory - one copy for all processes
Easy updates - replace library without recompiling
Faster linking - less code to process

Cons of Dynamic Linking

Runtime dependencies - requires library files
Version conflicts - "DLL hell"
Slower startup - library loading overhead
Complex deployment - must distribute libraries


Symbol Visibility

Control which symbols are exported in shared libraries:

// header.h
#ifdef _WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __attribute__((visibility("default")))
#endif

// Only this function is visible
EXPORT void public_function();

// This is internal
void internal_helper(); // Not exported
# Hide all symbols by default
g++ -fvisibility=hidden -fPIC -c utils.cpp

# Show exported symbols
nm -D libmylib.so

Hiding internal symbols reduces library size and link time.


Version Management

Shared Library Versioning (Linux)

# Create versioned library
g++ -shared -Wl,-soname,libmylib.so.1 utils.o -o libmylib.so.1.0.0

# Create symlinks
ln -s libmylib.so.1.0.0 libmylib.so.1
ln -s libmylib.so.1 libmylib.so

# ls -l:
# libmylib.so -> libmylib.so.1
# libmylib.so.1 -> libmylib.so.1.0.0
# libmylib.so.1.0.0
  • libmylib.so - Used by linker at compile time
  • libmylib.so.1 - Major version (ABI compatibility)
  • libmylib.so.1.0.0 - Full version (actual file)

ABI compatibility: Programs linked against 1.0.0 work with 1.0.1 (bug fixes) but not 2.0.0 (breaking changes).


Plugin Systems

Dynamic loading allows loading code at runtime without relinking:

#include <dlfcn.h>  // POSIX

// Load library at runtime
void* handle = dlopen("./plugin.so", RTLD_LAZY);
if (!handle) {
std::cerr << "Error: " << dlerror() << "\n";
return;
}

// Get function pointer
typedef void (*plugin_func_t)();
plugin_func_t plugin_func = (plugin_func_t)dlsym(handle, "plugin_init");

// Call plugin function
plugin_func();

// Unload library
dlclose(handle);

This enables plugin architectures where modules can be added without recompiling the main program.


Practical Recommendations

Use Static When

  • Building standalone executables
  • Embedding in restricted environments
  • Controlling exact dependency versions
  • Simplifying deployment
# Example: Static linking for distribution
g++ -O3 -static app.cpp -o app
strip app # Remove debug symbols
# Single executable, works anywhere

Use Dynamic When

  • Library used by multiple programs
  • Need to update libraries independently
  • Memory efficiency matters
  • Platform standard (Windows, Linux desktop)
# Example: Dynamic linking for shared use
g++ -fPIC -shared lib.cpp -o libmyapp.so
g++ app.cpp -o app -L. -lmyapp -Wl,-rpath,'$ORIGIN'
# Small app, shared library

Hybrid Approach

# Link standard libraries dynamically, custom statically
g++ app.cpp libmylib.a -o app -lm -lpthread
# libmylib.a: static
# libm, libpthread: dynamic

Troubleshooting

Library Not Found

./app
# error: libmylib.so: cannot open shared object file

Solutions:

# 1. Set LD_LIBRARY_PATH
export LD_LIBRARY_PATH=./libs:$LD_LIBRARY_PATH

# 2. Install to system path
sudo cp libmylib.so /usr/local/lib
sudo ldconfig # Update library cache

# 3. Use RPATH (embed path in executable)
g++ -Wl,-rpath,./libs main.cpp -o app

Version Mismatch

./app
# version `GLIBCXX_3.4.26' not found

Solution: Recompile or update library version.


Summary

Static Linking:

  • Library code → executable (self-contained)
  • Larger size, no dependencies, easy deployment
  • Use for: standalone apps, controlled environments

Dynamic Linking:

  • Library code → separate file (loaded at runtime)
  • Smaller size, shared memory, requires library files
  • Use for: shared libraries, system integration, plugins

Choice depends on:

  • Deployment complexity tolerance
  • Size constraints
  • Update frequency
  • Memory efficiency needs
# Quick decision helper
# Self-contained single binary? → Static
g++ -static app.cpp -o app

# System integration / plugins? → Dynamic
g++ -shared -fPIC lib.cpp -o lib.so
g++ app.cpp -o app -L. -llib