Static vs Shared Libraries

 

Static vs Shared Libraries
TLPI Chapter 41.13 — When to Use Static Libraries, Forced Static Linking Options
3
Ways to Force Static
4+
Code Examples
10
Interview Q&As

Key Concepts
Static Library (.a) Shared Library (.so) ar command -static gcc flag -Wl,-Bstatic -Wl,-Bdynamic chroot jail Self-contained binary

Shared is Usually Better — But Not Always

Shared libraries are the modern default for good reason: they save disk space, allow all programs to share one copy in memory, and allow library bug fixes to benefit all programs at once (by just replacing the .so file). But there are genuine situations where static linking is the right choice.

This section explains when static libraries are appropriate and shows you the three ways to force the linker to use them.

Static vs Shared: Side-by-Side Comparison
Property Static Library (.a) Shared Library (.so)
Code location at run time Copied into the executable Remains in a separate .so file
Executable size Larger Smaller
Memory usage (multiple programs) Each process has its own copy Single shared copy in RAM
Run without shared lib installed Yes — everything is inside the binary No — .so must be present
Library update benefits running programs No — must relink Yes — replace .so, all programs benefit
Immune to library ABI changes Yes — uses exact version it was linked with No — may break on incompatible .so upgrade
Works in chroot jail / minimal environment Yes Only if .so files are copied in too
Dynamic loading / plugins Not possible Yes (dlopen)

When Static Libraries Make Sense
🔒
No Installation Control

The target machine may not have the right shared libraries installed, and you can’t install them (e.g., delivering a binary to a customer’s server).

🏚️
Minimal Environments

chroot jails, containers without /lib, embedded systems with read-only filesystems, or rescue shells where shared libs aren’t available.

🛡️
ABI Stability

You need the exact library version you tested with. A compatible shared library upgrade might silently introduce a bug. Statically linked binaries are immune.

📦
Single-File Distribution

You want a self-contained single binary that users can just download and run — no installer, no dependency management (think busybox, Go binaries).

📝 Even a compatible shared library upgrade may unintentionally introduce a regression. Static linking pins your application to the exact library code you tested, guaranteeing bit-for-bit identical behavior.

Building and Using Static Libraries
/* math_ops.c — source for our static library */
#include "math_ops.h"

int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int subtract(int a, int b) { return a - b; }
/* math_ops.h */
int add(int a, int b);
int multiply(int a, int b);
int subtract(int a, int b);
# Step 1: Compile to object file (NO -fPIC needed for static libs)
gcc -c -Wall math_ops.c -o math_ops.o

# Step 2: Create the static library archive (.a file)
ar rcs libmath_ops.a math_ops.o
# ar options: r=insert, c=create, s=add index (for faster linking)

# Check what's inside the archive
ar t libmath_ops.a
# Output: math_ops.o

# Step 3: Link the application against the static library
gcc -o myapp main.c -L. -lmath_ops
# or explicitly: gcc -o myapp main.c ./libmath_ops.a

# Verify: no runtime dependency on libmath_ops
ldd myapp
# libmath_ops does NOT appear — its code is inside myapp itself
/* main.c */
#include <stdio.h>
#include "math_ops.h"

int main(void) {
    printf("3 + 4 = %d\n", add(3, 4));
    printf("3 * 4 = %d\n", multiply(3, 4));
    printf("7 - 2 = %d\n", subtract(7, 2));
    return 0;
}

Default: Shared Library Wins When Both Exist

If both libdemo.so and libdemo.a exist in the same directory and you link with -ldemo, the linker automatically prefers the shared version. You have to explicitly request static.

# Both exist: libdemo.so AND libdemo.a
ls ./libs/
# libdemo.a  libdemo.so

# Default: shared library is used
gcc -o prog main.c -L./libs -ldemo
ldd prog
# libdemo.so => ./libs/libdemo.so   <-- shared is chosen

# Check file size difference
ls -lh prog   # smaller (shared)

Three Ways to Force Static Linking

Method 1: Give the explicit .a file path

# Specify the .a file directly — no ambiguity
gcc -o prog main.c ./libs/libdemo.a

# Can also use absolute path
gcc -o prog main.c /usr/local/lib/libdemo.a

Method 2: -static flag (link EVERYTHING statically)

# Link everything statically, including libc
gcc -o prog main.c -static -L./libs -ldemo

# The resulting binary is completely self-contained
# but VERY large (includes libc, libm, etc.)
ldd prog
#   not a dynamic executable   <-- fully static!

# Check size — much larger
ls -lh prog
# Typical: 800KB+ vs 8KB for dynamic version
⚠️ -static links everything statically, including glibc. This makes very large binaries. Some Linux features (like NSS for hostname resolution) don’t work well with a statically linked glibc.

Method 3: -Wl,-Bstatic and -Wl,-Bdynamic (selective static linking)

# Link only libdemo statically, but other libraries dynamically
# The linker processes these flags left-to-right

gcc -o prog main.c \
    -Wl,-Bstatic -L./libs -ldemo \
    -Wl,-Bdynamic -lm -lpthread

# Effect:
#   libdemo → static (copied into binary)
#   libm, libpthread → dynamic (loaded at run time from .so)

# This is the most flexible approach:
# use static for YOUR library, dynamic for system libraries
# Practical example: embed OpenSSL statically, use system libc dynamically
gcc -o secure_app main.c \
    -Wl,-Bstatic \
    -L/opt/openssl-static/lib -lssl -lcrypto \
    -Wl,-Bdynamic \
    -lc

# Result: OpenSSL code is baked in, libc is still dynamic
ldd secure_app
# Shows: libc.so => ...   (dynamic)
# Does NOT show libssl or libcrypto (static)

Archive Linking Order — Circular Dependencies

With static libraries, link order matters. The linker scans archives left-to-right and only includes object files that satisfy unresolved symbols at that point. Circular dependencies between static libraries require listing a library multiple times.

# libA needs a symbol from libB, and libB needs a symbol from libA
# Solution: list libA twice (or use --start-group / --end-group)

gcc -o prog main.c -lA -lB -lA   # list libA again at the end

# Or use the group option (scans the group repeatedly until resolved):
gcc -o prog main.c -Wl,--start-group -lA -lB -Wl,--end-group

Interview Questions & Answers
Q1. What is a static library and how does it differ from a shared library?
A static library (.a file) is an archive of compiled object files. When you link against it, the linker copies the needed object code directly into your executable. A shared library (.so file) is loaded at run time; the executable only contains a reference to it, and all programs share one copy in memory. Static libraries produce self-contained but larger binaries; shared libraries produce smaller binaries that require the .so to be present at run time.
Q2. When should you prefer static over shared libraries?
1. Target environment where shared libraries are unavailable (chroot jails, minimal containers, embedded systems).
2. When users cannot or should not install shared libraries (single-binary distribution).
3. When you need immunity to shared library upgrades that might break your application.
4. For security-critical programs where you want to pin exact library versions.
Q3. What command creates a static library from object files?
ar rcs libname.a file1.o file2.o file3.o. The options mean: r=insert/replace members, c=create archive if it doesn’t exist, s=add a symbol index (makes linking faster). The ar (archiver) command is essentially tar for object files.
Q4. When both libfoo.so and libfoo.a exist, which does gcc prefer with -lfoo?
gcc prefers the shared library (.so) by default. To force the static version you must either: specify the .a file path directly, use -static (links everything statically), or use -Wl,-Bstatic before -lfoo to tell the linker to prefer static for that specific library.
Q5. What is the difference between -static and -Wl,-Bstatic?
-static forces ALL libraries (including glibc) to link statically, creating a fully self-contained binary. -Wl,-Bstatic is a toggle that affects only the libraries listed after it on the command line, until -Wl,-Bdynamic is encountered. This selective approach lets you statically link one specific library while keeping others dynamic.
Q6. Does a static library need to be compiled with -fPIC?
No. Position-Independent Code (-fPIC) is required for shared libraries because the .so can be loaded at any address in the process’s address space. For static libraries, the code is copied into the executable and linked at a fixed address, so -fPIC is unnecessary (though harmless).
Q7. A statically linked program is 10x larger than a dynamically linked one. Why?
The static binary contains a copy of ALL the library code it uses, including the entire glibc runtime (printf, malloc, string functions, etc.). The dynamic binary only contains a small stub pointing to the shared .so files; those files are loaded at run time and shared among all programs that need them. A typical “hello world” statically linked against glibc is ~800KB; dynamically linked it’s ~8KB.
Q8. Can you mix static and shared libraries in the same gcc command?
Yes, using -Wl,-Bstatic and -Wl,-Bdynamic as toggles. For example: gcc main.c -Wl,-Bstatic -lmylib -Wl,-Bdynamic -lm -lpthread — this links libmylib statically and libm/libpthread dynamically. The linker processes these flags in order, left to right.
Q9. What problem can arise with link order when using static libraries?
The static linker scans archives left-to-right. If libA references a symbol in libB, and libB is listed before libA on the command line, the linker won’t know to pull in the needed code from libA because it processes libB first. Solution: list libraries in dependency order (dependencies after dependents), or list a library multiple times, or use -Wl,–start-group…-Wl,–end-group to make the linker scan the group repeatedly.
Q10. How do you verify whether a binary is statically or dynamically linked?
ldd ./binary: shows dynamic dependencies; for a static binary it says “not a dynamic executable”
file ./binary: shows “statically linked” or “dynamically linked”
readelf -d ./binary | grep NEEDED: shows DT_NEEDED entries (empty for fully static binary)

Leave a Reply

Your email address will not be published. Required fields are marked *