Post

Static Library vs Dynamic Library

Static Library vs Dynamic Library

When your program calls a function that lives in another file, the linker has to find it. Libraries are how we package reusable code, and there are two types: static (.a) and dynamic/shared (.so). This post covers how each is built, the difference between the two, and of course how it works.


Overview

 Static (.a)Dynamic (.so)
Windows equivalent.lib.dll
Linked atbuild timeload time (or run time)
Code ends upcopied into the executableshared, mapped from the .so file
Executable sizelargersmaller
Update a librarymust relink the appreplace the .so (if ABI compatible)
Needs library on targetnoyes

Example

Examples to get a better understanding:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* mathlib.h */
int add(int a, int b);

/* mathlib.c */
#include "mathlib.h"
int add(int a, int b) { 
    return a + b;
}

/* main.c */
#include <stdio.h>
#include "mathlib.h"
int main(void) { 
    printf("%d\n", add(2, 3));
    return 0; 
}

Static Library

A static library is just an archive of object files made by ar.

1
2
3
gcc -c mathlib.c -o mathlib.o
ar rcs libmath.a mathlib.o        # create archive, this only contain 1 .o file
gcc main.c -L. -lmath -o app_static
  • -L. tells the linker where to search(. is the current directory), -lmath means libmath.a (or .so).
  • The linker pulls only the object files that are actually referenced, and copies them into app_static. (hence the application would be bigger)
  • The result is self-contained: no library needed at run time.

Inspect it:

1
2
3
4
5
$ ar t libmath.a          # list members
mathlib.o
$ nm libmath.a            # inspect symbols
mathlib.o:
0000000000000000 T add

Dynamic (Shared) Library

Shared objects must be built as position-independent code (-fPIC) so that they can be mapped at any address.

1
2
3
gcc -fPIC -c mathlib.c -o mathlib.o  #compile to object file
gcc -shared -o libmath.so mathlib.o  #link object into shared lib
gcc main.c -L. -lmath -o app_dynamic # build app with shared lib

The executable only stores a reference to libmath.so. At startup the dynamic linker (ld-linux.so) finds and maps it:

1
2
3
4
5
$ ./app_dynamic
error while loading shared libraries: libmath.so: cannot open shared object file

$ LD_LIBRARY_PATH=. ./app_dynamic     # works
5

Ways to tell the loader where to look:

  • LD_LIBRARY_PATH - quick, for development only.
  • Install into /usr/lib or /usr/local/lib and run sudo ldconfig.

Inspect it:

1
2
3
ldd app_dynamic                 # which .so files it needs
readelf -d app_dynamic | grep NEEDED
nm -D libmath.so                # exported symbols

Versioning and soname

Convention for shared libraries use a symlink chain:

1
2
3
libmath.so        -> libmath.so.1        (used by the linker at build time)
libmath.so.1      -> libmath.so.1.0.0    (soname, used by the loader at run time)
libmath.so.1.0.0                         (the real file)
1
gcc -shared -Wl,-soname,libmath.so.1 -o libmath.so.1.0.0 mathlib.o

Changing the Library: Rebuild vs Replace

The real difference shows up when the library implementation changes. Build the same main.c both ways, keeping the libraries in separate folders:

1
2
3
4
5
6
7
8
9
10
11
mkdir -p s d
gcc -fPIC -c mathlib.c
ar rcs s/libmath.a mathlib.o                  # static lib
gcc -shared -o d/libmath.so mathlib.o         # dynamic lib

gcc main.c -Ls -l:libmath.a -o app_static
gcc main.c -Ld -lmath -Wl,-rpath,'$ORIGIN/d' -o app_dynamic

$ ./app_static;  ./app_dynamic
add(2, 3) = 5
add(2, 3) = 5

Now change the implementation in mathlib.c (here a + b becomes a * b) and rebuild only the library:

1
2
3
4
5
6
sed -i 's/a + b/a * b/' mathlib.c
gcc -fPIC -shared -o d/libmath.so mathlib.c   # new .so, apps untouched

$ ./app_static;  ./app_dynamic
add(2, 3) = 5      # static: still the old code, it was copied into the binary
add(2, 3) = 6      # dynamic: new code, loaded from the new .so at startup

The static app only picks up the change after the library and the app are rebuilt:

1
2
3
4
5
gcc -c mathlib.c && ar rcs s/libmath.a mathlib.o
gcc main.c -Ls -l:libmath.a -o app_static     # relink required

$ ./app_static
add(2, 3) = 6

This is the whole trade-off: with a .so you ship one file and every app gets the fix on its next start, with a .a you must relink and redeploy every app.


Runtime Risks of Dynamic Libraries

Because linking is finished at run time, a dynamic app can build fine and still fail on the target machine. There are two common cases.

1. Missing library

The loader cannot find the .so the executable asks for, so the app does not even start:

1
2
$ ./app
./app: error while loading shared libraries: libmath.so: cannot open shared object file: No such file or directory

Check with ldd app (look for not found). Typical causes: library not installed, wrong rpath / LD_LIBRARY_PATH, or a different distro or version on the target.

2. ABI break

The .so is replaced by a version whose interface changed, but the app is not rebuilt. Here add gains a third parameter:

1
2
/* v1.c */  int add(int a, int b)         { return a + b; }
/* v2.c */  int add(int a, int b, int c)  { return a + b + c; }
1
2
3
4
5
$ gcc -fPIC -shared -o libmath.so v1.c && ./app
add(2, 3) = 5

$ gcc -fPIC -shared -o libmath.so v2.c && ./app    # app NOT rebuilt
add(2, 3) = -736414659 (unidentified)

How to reduce the risk:

  • Use the soname scheme above and bump the major version on any ABI break
  • Keep structs opaque (pointer + accessor functions) so their layout can change safely.
  • Ship the exact .so version you tested with, and check with ldd on the target.

With a static library none of this can happen at run time: any mismatch is a compile or link error, found before you ship.


Which One to Choose?

Prefer static when

  • Bare-metal / MCU firmware: there is no OS loader, so everything is often linked statically.
  • You want a single self-contained binary (containers, scratch images, tools shipped to unknown machines).
  • You want link-time optimization across the library boundary.

Prefer dynamic when

  • Many programs share one library (saves disk and RAM).
  • You need to patch a library (e.g. a security fix in OpenSSL) without rebuilding every app.

This post is licensed under CC BY 4.0 by the author.