top of page

Static vs Dynamic Libraries in Linux and Linking Executables

  • Writer: Ashok Kumar Kumawat
    Ashok Kumar Kumawat
  • Apr 5
  • 7 min read

Prerequisites

Before diving in, make sure you have:

  • A Linux environment (Ubuntu, Fedora, etc.)

  • GCC compiler (sudo apt install build-essential on Ubuntu)

  • Basic knowledge of C programming (printf, scanf, functions)

  • Familiarity with terminal commands (ls, nm, ldd)


Terminal commands

1. ls

  • Purpose: Lists files in a directory.

  • Usage for libraries: Helps you see whether .a (static) or .so (dynamic) files exist.

  • Command:

    bash

    ls --color=auto /usr/lib | grep libc

  • Output example:

    Code

    libc.a libc.so libc.so.6

👉 Colors may differ: .so often shows in green (shared object), .a in default color.


2. nm

  • Purpose: Shows symbols (functions, variables) inside object files or libraries.

  • Usage for libraries: Lets you check if functions like printf are defined inside a library.

  • Command:

    bash

    nm -D /usr/lib/x86_64-linux-gnu/libc.so.6 | grep printf

  • Output example:

    Code

    0000000000055410 T printf

👉 T means the symbol is defined (text section = function code).


3. ldd

  • Purpose: Lists dynamic libraries required by an executable.

  • Usage for libraries: Lets you see whether your program depends on dynamic .so files.

  • Command:

    bash

    ldd ./prog_dynamic

  • Output example:

    Code

    libmylib.so => ./libmylib.so (0x00007f...) libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007f...) /lib64/ld-linux-x86-64.so.2 (0x00007f...)

👉 Confirms dynamic linking.


How They Fit Together

  • ls → Identify what libraries exist (static .a or dynamic .so).

  • nm → Inspect what functions are inside those libraries.

  • ldd → Check what your executable actually links to at runtime.

Understanding Libraries

  • Static Library (.a): Code is copied into the executable at compile time.

  • Dynamic Library (.so): Code is linked at runtime by the dynamic linker.

  • C Standard Library (glibc): Provides printf, scanf, malloc, etc. → usually dynamic by default.


Small Code Example

Let’s write a simple program that uses both user-defined and standard functions.

main.c

c

#include <stdio.h>
#include "mylib.h"   // our custom library header

int main() {
    hello();                 // custom function
    printf("Enter a number: ");
    int x;
    scanf("%d", &x);         // standard library function
    printf("You entered: %d\n", x);
    return 0;
}

mylib.c

c

#include <stdio.h>

void hello() {
    printf("Hello from mylib!\n");
}

mylib.h

c

void hello();

Example 1 – Static Linking

bash

gcc -c mylib.c -o mylib.o
ar rcs libmylib.a mylib.o
gcc main.c -L. -lmylib -static -o prog_static

What each command does:

  • gcc -c mylib.c -o mylib.o : compile mylib.c to object file.

  • ar rcs libmylib.a mylib.o : create static archive libmylib.a.

  • gcc main.c -L. -lmylib -static -o prog_static : link main.c with libmylib.a and force static linking for all libraries (including libc).

Code Recap

Compile source into object file

bash

gcc -c mylib.c -o mylib.o
  • gcc compiles mylib.c into machine code.

  • -c means compile only, don’t link yet.

  • Output: mylib.o (object file).

👉 This is the raw compiled code for your custom function(s).

Create static library archive

bash

ar rcs libmylib.a mylib.o
  • ar = archive tool (bundles object files into .a).

  • rcs flags:

    • r → replace/add file into archive

    • c → create archive if it doesn’t exist

    • s → add an index for faster linking

  • Output: libmylib.a (static library).

👉 This is your reusable static library containing hello().

Link with static library

bash

gcc main.c -L. -lmylib -static -o prog_static
  • main.c → your main program.

  • -L. → look for libraries in current directory.

  • -lmylib → link with libmylib.a (prefix lib + suffix .a are implied).

  • -static → force static linking (no .so files).

  • -o prog_static → output executable named prog_static.

👉 Result: prog_static is a fully self-contained executable. It includes both your custom hello() and standard functions like printf baked in.

Verification

Run:

bash

ldd prog_static

Output:

Code

not a dynamic executable

👉 Confirms everything is statically linked.

Why This Matters

  • Static linking makes your program portable (no external .so needed).

  • But it increases size (because libc and your library code are copied inside).

  • Dynamic linking is smaller and more flexible, but depends on system libraries.

Linker-stage verbose check (example):

bash

gcc main.c -L. -lmylib -static -o prog_static -Wl,-v

Sample relevant lines you may see from the linker:

Code

attempt to open /usr/lib/gcc/x86_64-linux-gnu/9/libgcc.a succeeded
attempt to open /usr/lib/x86_64-linux-gnu/libc.a succeeded

Runtime Check output:

bash

ldd prog_static

Output:

Code

not a dynamic executable

Notes:

  • prog_static contains code for hello() and libc functions like printf and scanf.

  • File size will be significantly larger than dynamically linked executable.


Example 2 – Dynamic Linking (Custom Library)

bash

gcc -fPIC -c mylib.c -o mylib.o
gcc -shared -o libmylib.so mylib.o
gcc main.c -L. -lmylib -o prog_dynamic

What each command does:

  • -fPIC produces position-independent code required for shared libraries.

  • gcc -shared -o libmylib.so mylib.o creates libmylib.so.

  • gcc main.c -L. -lmylib -o prog_dynamic links main.c against libmylib.so (dynamic).

Code Recap

Compile position-independent code

bash

gcc -fPIC -c mylib.c -o mylib.o
  • -fPIC → Position Independent Code. This is required for shared libraries because they can be loaded at different memory addresses at runtime.

  • -c → compile only, don’t link yet.

  • Output: mylib.o (object file ready for dynamic linking).

👉 Without -fPIC, you might get relocation errors when creating .so.

Create shared library

bash

gcc -shared -o libmylib.so mylib.o
  • -shared → tells GCC to produce a shared object (.so).

  • Output: libmylib.so (dynamic library).

👉 This is your reusable dynamic library containing hello().

Link main program with shared library

bash

gcc main.c -L. -lmylib -o prog_dynamic
  • main.c → your main program.

  • -L. → look for libraries in current directory.

  • -lmylib → link with libmylib.so (prefix lib + suffix .so are implied).

  • Output: prog_dynamic (executable).

👉 At this stage, the executable doesn’t contain the full code of hello(). Instead, it stores a reference to libmylib.so.

Runtime Linking

When you run ./prog_dynamic, the dynamic linker (ld-linux.so) loads:

Check with:

bash

ldd prog_dynamic

Output:

Code

libmylib.so => ./libmylib.so (0x00007f...)
libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)
/lib64/ld-linux-x86-64.so.2 (0x00007f...)

👉 Confirms dynamic linking: both your custom library and glibc are loaded at runtime.

Key Differences vs Static

  • Static (.a): Code copied into executable → no external dependency.

  • Dynamic (.so): Executable depends on external library → smaller size, shared in memory.

Linker-stage verbose check:

bash

gcc main.c -L. -lmylib -o prog_dynamic -Wl,-v

Sample relevant lines:

Code

attempt to open /usr/lib/x86_64-linux-gnu/libc.so succeeded

Runtime Check output:

bash

ldd prog_dynamic

Output:

Code

libmylib.so => ./libmylib.so (0x00007f...)
libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)
/lib64/ld-linux-x86-64.so.2 (0x00007f...)

Notes:

  • hello() is resolved from libmylib.so at runtime.

  • printf and scanf are resolved from libc.so.6 at runtime.

  • If libmylib.so is not in a standard path, run LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./prog_dynamic or install the .so into a system library path.


Example 3 – Mixed Linking

Suppose you want your custom library static, but standard functions dynamic:

bash

gcc -c mylib.c -o mylib.o
ar rcs libmylib.a mylib.o
gcc main.c -L. -lmylib -o prog_mixed

What each command does:

  • Create libmylib.a as in Example 1.

  • Link main.c with libmylib.a but do not use -static. The linker will include hello() from the static archive, while standard libraries (libc) remain dynamically linked.

Code Recap

Step 1: Compile your custom library source

bash

gcc -c mylib.c -o mylib.o
  • Compiles mylib.c into an object file (mylib.o).

  • -c means compile only, don’t link yet. 👉 This is the raw machine code for your hello() function.

Step 2: Create a static library archive

bash

ar rcs libmylib.a mylib.o
  • ar bundles object files into a static library (.a).

  • Flags:

    • r → replace/add file

    • c → create archive

    • s → add index for faster linking 👉 Now you have libmylib.a, a static library containing your custom function.

Step 3: Link main program with static library

bash

gcc main.c -L. -lmylib -o prog_mixed
  • main.c → your main program.

  • -L. → look for libraries in current directory.

  • -lmylib → link with libmylib.a (prefix lib + suffix .a are implied).

  • Output: prog_mixed (executable).

👉 Notice: no -static flag here. That means:

  • Your custom library (libmylib.a) is linked statically.

  • Standard C library (libc.so.6) is linked dynamically.

Verification

Run:

bash

ldd prog_mixed

Output:

Code

libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6
/lib64/ld-linux-x86-64.so.2

👉 Confirms:

  • hello() is baked into the executable (from libmylib.a).

  • printf and scanf are dynamically linked (from libc.so.6).

Why This Is “Mixed”

  • Your custom functions → static (no external dependency).

  • Standard library functions (printf, scanf) → dynamic (shared at runtime).

This is actually the default behavior when you link with a .a library but don’t force -static.

Check output:

bash

ldd prog_mixed

Output:

Code

libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)
/lib64/ld-linux-x86-64.so.2 (0x00007f...)

Notes:

  • hello() is part of the executable (static).

  • printf and scanf remain dynamic (from libc.so.6).

  • This is a common pattern: static custom libraries + dynamic system libraries.

Useful inspection commands and how to interpret them

  1. ls --color=auto

  2. Use to list files and see .a and .so files.

  3. Color depends on LS_COLORS configuration; .so often appears with executable/shared-object color, .a appears as a regular file.

Example:

bash

ls -l

Possible output lines:

Code

-rw-r--r-- 1 user user  1234 Apr  5 20:00 libmylib.a
-rwxr-xr-x 1 user user  5678 Apr  5 20:01 libmylib.so
-rwxr-xr-x 1 user user 123456 Apr  5 20:02 prog_dynamic
  1. nm — inspect symbols

  2. Inspect static archive:

bash

nm libmylib.a | grep hello
  • Inspect shared object (dynamic symbols):

bash

nm -D libmylib.so | grep hello

Interpretation:

  • T or t indicates a defined function symbol in the text section.

  • U indicates an undefined symbol (the object expects it from elsewhere).

  • ldd — list dynamic dependencies of an executable

  • ldd prog_dynamic shows which .so files the executable will load at runtime.

  • If ldd prints not a dynamic executable, the binary is statically linked.

  • Linker verbose output

  • Add -Wl,-v to gcc to see what the linker attempts to open and which libraries it finds.

  • Example:

bash

gcc main.c -L. -lmylib -o prog_dynamic -Wl,-v

Look for lines like:

Code

attempt to open /usr/lib/x86_64-linux-gnu/libc.so succeeded

or for static linking:

Code

attempt to open /usr/lib/x86_64-linux-gnu/libc.a succeeded

Side-by-side summary table

Aspect

Static (prog_static)

Dynamic (prog_dynamic)

Mixed (prog_mixed)

Custom library file used

libmylib.a

libmylib.a

Command to create library

ar rcs libmylib.a mylib.o

gcc -shared -o libmylib.so mylib.o

ar rcs libmylib.a mylib.o

Link command example

gcc main.c -L. -lmylib -static -o prog_static

gcc main.c -L. -lmylib -o prog_dynamic

gcc main.c -L. -lmylib -o prog_mixed

libc linking

static (inside exe)

dynamic (libc.so.6)

dynamic (libc.so.6)

ldd output

not a dynamic executable

lists libc.so.6

Executable size

larger

smaller

medium

Practical tips and common pitfalls

  • When building .so, always compile with -fPIC for position-independent code on most architectures. Omit -fPIC only for special cases where you know it is safe.

  • If a dynamically linked executable cannot find a .so at runtime, you will see an error like:

    Code

    error while loading shared libraries: libmylib.so: cannot open shared object file: No such file or directory

    Fix by placing the .so in a standard library path, using LD_LIBRARY_PATH, or configuring /etc/ld.so.conf.d/ and running ldconfig.

  • Static linking of glibc (-static) can cause portability issues across different kernel versions and glibc expectations; prefer dynamic libc unless you have a strong reason to statically link everything.

Final concise checklist (commands you will use)

  • Compile object: gcc -c mylib.c -o mylib.o

  • Create static lib: ar rcs libmylib.a mylib.o

  • Create shared lib: gcc -fPIC -c mylib.c -o mylib.o then gcc -shared -o libmylib.so mylib.o

  • Link static (all static): gcc main.c -L. -lmylib -static -o prog_static

  • Link dynamic (shared lib): gcc main.c -L. -lmylib -o prog_dynamic

  • Link mixed (static custom, dynamic libc): gcc main.c -L. -lmylib -o prog_mixed

  • Inspect dynamic deps: ldd prog_dynamic

  • Inspect symbols: nm libmylib.a or nm -D libmylib.so

  • Linker verbose: gcc ... -Wl,-v

This single consolidated output contains the code, the commands for static/dynamic/mixed linking, the expected linker/runtime outputs, and the inspection commands you will use to verify what the linker did.

Comments


© 2035 by Robert Caro. Powered and secured by Wix

bottom of page