Module 8.3

Static and Dynamic Libraries

Learn to package your code into reusable libraries that can be shared across projects. Master the creation of static archives and dynamic shared libraries, understand linking mechanisms, and manage library dependencies like a professional C developer.

40 min read
Intermediate
Hands-on Examples
What You'll Learn
  • Static vs dynamic libraries
  • Creating static libraries (.a)
  • Creating shared libraries (.so/.dll)
  • Linking and using libraries
  • Library paths and management
Contents
01

What Are Libraries?

Libraries are collections of pre-compiled code that you can reuse across multiple programs. Instead of copying source files everywhere, you package related functions into a library that any program can link against and use.

Why Use Libraries?

When you call printf(), the actual code does not exist in your program. It lives in the C standard library, which gets linked to your executable. Libraries provide several key benefits for software development:

Code Reuse

Write code once, use it in many programs. No need to copy source files between projects or maintain multiple versions of the same code.

Faster Compilation

Libraries are pre-compiled. Your build only needs to link against them, not recompile thousands of lines of library code every time.

Encapsulation

Users only see the header file interface. Implementation details stay hidden in the compiled library, protecting intellectual property.

Easy Updates

Fix a bug in the library once, and all programs using it benefit. With dynamic libraries, you do not even need to recompile the programs.

Concept

Library

A library is a collection of pre-compiled object files packaged together, providing functions, data types, and variables that programs can use. Libraries come in two main types: static (linked at compile time) and dynamic (linked at runtime).

Key insight: You have been using libraries all along! The C standard library (libc) provides printf, malloc, string functions, and more.

Static vs Dynamic Libraries

Aspect Static Library Dynamic Library
Extension .a (Linux/Mac), .lib (Windows) .so (Linux), .dylib (Mac), .dll (Windows)
Linking Time Compile time (static linking) Runtime (dynamic linking)
Executable Size Larger (library code copied in) Smaller (references library)
Memory Usage Each program has own copy Shared between programs
Distribution Self-contained executable Must ship library files
Updates Recompile program needed Just replace library file

Library Naming Conventions

# Linux/Unix library naming
libmath.a       # Static library named "math"
libmath.so      # Dynamic library named "math"
libmath.so.1    # Dynamic library with version 1
libmath.so.1.0  # Dynamic library version 1.0

# When linking, drop "lib" prefix and extension:
gcc main.c -lmath    # Links against libmath.a or libmath.so

# Windows naming
math.lib        # Static library
math.dll        # Dynamic library
Practice Questions

Task: Identify each file as static (S) or dynamic (D) library:

1. libcrypto.a
2. libssl.so.1.1
3. kernel32.dll
4. libpthread.a
5. libc.so.6
Show Solution
  1. libcrypto.a - Static (.a extension)
  2. libssl.so.1.1 - Dynamic (.so extension)
  3. kernel32.dll - Dynamic (.dll extension)
  4. libpthread.a - Static (.a extension)
  5. libc.so.6 - Dynamic (.so extension, version 6)

Task: For each scenario, recommend static or dynamic library:

  1. A single executable that must run on any Linux system without dependencies
  2. A library used by 10 different programs on the same system
  3. Security-critical code that needs frequent patches
  4. Embedded system with very limited storage
Show Solution
  1. Static - Self-contained, no external dependencies needed
  2. Dynamic - Shared memory, single copy serves all programs
  3. Dynamic - Can update library without recompiling programs
  4. Dynamic - Smaller executables, shared library saves storage
02

Creating Static Libraries

Static libraries are archives of object files that get copied into your executable at link time. They are simple to create and use, making them a great starting point for packaging reusable code.

The ar Command

The ar (archiver) command creates and manipulates static library archives. Think of it like a zip file for object files, but without compression.

# Basic ar syntax
ar [options] archive_name object_files

# Common options:
# r - Insert/replace files in archive
# c - Create archive (suppress warning if new)
# s - Create/update index (for faster linking)
# t - List contents of archive
# x - Extract files from archive

Step-by-Step: Creating a Static Library

Let us create a math utilities library from scratch:

// mathlib.h - Header file (interface)
#ifndef MATHLIB_H
#define MATHLIB_H

int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);
int factorial(int n);
int fibonacci(int n);

#endif
// add.c
#include "mathlib.h"

int add(int a, int b) {
    return a + b;
}

// subtract.c
#include "mathlib.h"

int subtract(int a, int b) {
    return a - b;
}

// multiply.c
#include "mathlib.h"

int multiply(int a, int b) {
    return a * b;
}

// divide.c
#include "mathlib.h"

double divide(int a, int b) {
    if (b == 0) return 0.0;
    return (double)a / b;
}

// factorial.c
#include "mathlib.h"

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

// fibonacci.c
#include "mathlib.h"

int fibonacci(int n) {
    if (n <= 0) return 0;
    if (n == 1) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

Building the Static Library

# Step 1: Compile each source file to object file
gcc -c add.c -o add.o
gcc -c subtract.c -o subtract.o
gcc -c multiply.c -o multiply.o
gcc -c divide.c -o divide.o
gcc -c factorial.c -o factorial.o
gcc -c fibonacci.c -o fibonacci.o

# Or compile all at once:
gcc -c add.c subtract.c multiply.c divide.c factorial.c fibonacci.c

# Step 2: Create the static library archive
ar rcs libmathlib.a add.o subtract.o multiply.o divide.o factorial.o fibonacci.o

# Step 3: Verify the library contents
ar -t libmathlib.a
# Output:
# add.o
# subtract.o
# multiply.o
# divide.o
# factorial.o
# fibonacci.o

Using the Static Library

// main.c - Using our library
#include <stdio.h>
#include "mathlib.h"

int main() {
    printf("5 + 3 = %d\n", add(5, 3));
    printf("10 - 4 = %d\n", subtract(10, 4));
    printf("6 * 7 = %d\n", multiply(6, 7));
    printf("15 / 3 = %.2f\n", divide(15, 3));
    printf("5! = %d\n", factorial(5));
    printf("Fib(10) = %d\n", fibonacci(10));
    return 0;
}
# Compile and link with the static library
gcc main.c -L. -lmathlib -o calculator

# -L.       : Look for libraries in current directory
# -lmathlib : Link against libmathlib.a

# Or specify the library directly:
gcc main.c libmathlib.a -o calculator

# Run the program
./calculator
# Output:
# 5 + 3 = 8
# 10 - 4 = 6
# 6 * 7 = 42
# 15 / 3 = 5.00
# 5! = 120
# Fib(10) = 55
Smart Linking: The linker only includes object files from the static library that contain symbols your program actually uses. If you never call fibonacci(), that code will not be in your final executable.
Practice Questions

Task: Write the commands to create a static library libstrutils.a from strlen.c, strcpy.c, and strcmp.c.

Show Solution
# Compile to object files
gcc -c strlen.c -o strlen.o
gcc -c strcpy.c -o strcpy.o
gcc -c strcmp.c -o strcmp.o

# Create static library
ar rcs libstrutils.a strlen.o strcpy.o strcmp.o

# Or in fewer commands:
gcc -c strlen.c strcpy.c strcmp.c
ar rcs libstrutils.a strlen.o strcpy.o strcmp.o

Task: This command fails with "cannot find -lmylib". What is wrong?

gcc main.c -lmylib -o program
# Error: cannot find -lmylib
Show Solution
# The linker cannot find the library because it is not in the 
# standard library search path.

# Solution 1: Specify library directory with -L
gcc main.c -L/path/to/lib -lmylib -o program

# Solution 2: If library is in current directory
gcc main.c -L. -lmylib -o program

# Solution 3: Specify full path to library
gcc main.c /path/to/libmylib.a -o program

# Solution 4: Add to LD_LIBRARY_PATH (temporary)
export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH
gcc main.c -lmylib -o program

Task: Create a complete static library for a stack data structure with push, pop, peek, isEmpty functions.

Show Solution
// stack.h
#ifndef STACK_H
#define STACK_H

#define STACK_SIZE 100

typedef struct {
    int items[STACK_SIZE];
    int top;
} Stack;

void stack_init(Stack *s);
int stack_push(Stack *s, int value);
int stack_pop(Stack *s, int *value);
int stack_peek(Stack *s, int *value);
int stack_isEmpty(Stack *s);

#endif

// stack.c
#include "stack.h"

void stack_init(Stack *s) { s->top = -1; }

int stack_push(Stack *s, int value) {
    if (s->top >= STACK_SIZE - 1) return 0;
    s->items[++s->top] = value;
    return 1;
}

int stack_pop(Stack *s, int *value) {
    if (s->top < 0) return 0;
    *value = s->items[s->top--];
    return 1;
}

int stack_peek(Stack *s, int *value) {
    if (s->top < 0) return 0;
    *value = s->items[s->top];
    return 1;
}

int stack_isEmpty(Stack *s) { return s->top < 0; }
# Build commands
gcc -c stack.c -o stack.o
ar rcs libstack.a stack.o

# Use in program
gcc main.c -L. -lstack -o program
03

Creating Dynamic Libraries

Dynamic libraries (also called shared libraries) are loaded at runtime rather than being copied into the executable. They enable memory sharing between programs and support updates without recompilation.

Position Independent Code (PIC)

Dynamic libraries must use Position Independent Code because they can be loaded at any memory address. The -fPIC flag tells GCC to generate code that works regardless of where it is loaded in memory.

# Compile with Position Independent Code
gcc -c -fPIC add.c -o add.o
gcc -c -fPIC subtract.c -o subtract.o
gcc -c -fPIC multiply.c -o multiply.o
gcc -c -fPIC divide.c -o divide.o

# Or all at once:
gcc -c -fPIC *.c

Creating the Shared Library

# Create shared library on Linux
gcc -shared -o libmathlib.so add.o subtract.o multiply.o divide.o

# Or compile and create in one step:
gcc -shared -fPIC -o libmathlib.so add.c subtract.c multiply.c divide.c

# Create with version information (recommended)
gcc -shared -Wl,-soname,libmathlib.so.1 -o libmathlib.so.1.0 *.o

# Create symbolic links for versioning
ln -s libmathlib.so.1.0 libmathlib.so.1    # soname link
ln -s libmathlib.so.1 libmathlib.so        # linker name link

Library Versioning

Proper versioning prevents compatibility issues when libraries are updated:

# Version naming convention: libname.so.MAJOR.MINOR.PATCH
libmathlib.so.1.2.3
#              │ │ │
#              │ │ └── Patch: Bug fixes (binary compatible)
#              │ └──── Minor: New features (binary compatible)
#              └────── Major: Breaking changes (incompatible)

# File structure after proper setup:
libmathlib.so       -> libmathlib.so.1      # Linker uses this
libmathlib.so.1     -> libmathlib.so.1.2.3  # Runtime loader uses this
libmathlib.so.1.2.3                          # Actual library file

Linking Against Dynamic Libraries

# Compile program with dynamic library
gcc main.c -L. -lmathlib -o calculator

# Check which libraries the program needs
ldd calculator
# Output:
#   linux-vdso.so.1 (0x00007fff...)
#   libmathlib.so => not found          # Problem!
#   libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
#   /lib64/ld-linux-x86-64.so.2

# The library is not found because it is not in the search path

Runtime Library Path

# Method 1: Set LD_LIBRARY_PATH (temporary)
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./calculator

# Method 2: Install to system directory (permanent, requires root)
sudo cp libmathlib.so /usr/local/lib/
sudo ldconfig

# Method 3: Embed path in executable (rpath)
gcc main.c -L. -lmathlib -Wl,-rpath,. -o calculator
# Now the executable knows to look in current directory

# Method 4: Use $ORIGIN for relative path
gcc main.c -L. -lmathlib -Wl,-rpath,'$ORIGIN' -o calculator
# Looks for library relative to executable location
Windows DLL Differences

On Windows, creating DLLs requires additional steps: exporting symbols with __declspec(dllexport) and importing with __declspec(dllimport). Windows searches the executable's directory first, unlike Linux.

Practice Questions

Task: Write commands to create libutils.so from utils.c.

Show Solution
# Method 1: Two steps
gcc -c -fPIC utils.c -o utils.o
gcc -shared -o libutils.so utils.o

# Method 2: One step
gcc -shared -fPIC -o libutils.so utils.c

# With version (best practice):
gcc -shared -fPIC -Wl,-soname,libutils.so.1 -o libutils.so.1.0 utils.c
ln -s libutils.so.1.0 libutils.so.1
ln -s libutils.so.1 libutils.so

Task: Your program runs fine during development but fails with "libcustom.so not found" when deployed. How do you fix it?

Show Solution
# Diagnosis: Check what libraries are needed
ldd ./program

# Solution 1: Ship library with program, use rpath
gcc main.c -L. -lcustom -Wl,-rpath,'$ORIGIN/lib' -o program
# Then put libcustom.so in ./lib/ relative to executable

# Solution 2: Install to standard location
sudo cp libcustom.so /usr/local/lib/
sudo ldconfig

# Solution 3: Add to /etc/ld.so.conf.d/
echo "/path/to/libs" | sudo tee /etc/ld.so.conf.d/custom.conf
sudo ldconfig

# Solution 4: Set LD_LIBRARY_PATH in startup script
#!/bin/bash
export LD_LIBRARY_PATH=/path/to/libs:$LD_LIBRARY_PATH
exec ./program "$@"
04

Linking and Using Libraries

Understanding how the linker finds and connects libraries to your program is essential for avoiding frustrating "undefined reference" and "library not found" errors.

The Linking Process

# Compilation phases:
# 1. Preprocessing: #include, #define expansion
# 2. Compilation: C code -> assembly
# 3. Assembly: assembly -> object files (.o)
# 4. Linking: object files + libraries -> executable

gcc -v main.c -L. -lmathlib -o program
# -v shows detailed compilation steps

Library Search Order

The linker searches for libraries in this order:

  1. -L directories: Paths specified with -L flag
  2. LIBRARY_PATH: Environment variable (compile time)
  3. System directories: /lib, /usr/lib, /usr/local/lib
  4. ld.so.conf: Directories listed in /etc/ld.so.conf

Static vs Dynamic Preference

# By default, linker prefers dynamic libraries
gcc main.c -L. -lmath -o program
# Links libmath.so if both .a and .so exist

# Force static linking for specific library
gcc main.c -L. -Wl,-Bstatic -lmath -Wl,-Bdynamic -o program

# Force all static linking
gcc -static main.c -L. -lmath -o program
# Warning: Creates larger executable, may have licensing issues

# Check what was linked
file program
# ELF 64-bit LSB executable, dynamically linked...

file program_static
# ELF 64-bit LSB executable, statically linked...

Link Order Matters!

The order of libraries on the command line is crucial. The linker processes left to right, resolving symbols as it goes:

# WRONG order - may cause undefined reference errors
gcc -lmathlib main.c -o program
# Linker sees mathlib first (nothing needs it yet)
# Then sees main.c which needs mathlib (too late!)

# CORRECT order - libraries after objects that use them
gcc main.c -lmathlib -o program
# Linker sees main.c, notes undefined symbols
# Then sees mathlib, resolves those symbols

# With multiple libraries that depend on each other:
# If libA uses libB, put libA before libB
gcc main.c -lA -lB -o program

Common Linker Errors

Error Cause Solution
undefined reference to 'func' Function not found in any library Add -l flag for library containing func
cannot find -lmylib Library file not in search path Add -L/path/to/lib or install library
multiple definition of 'var' Symbol defined in multiple places Use extern in headers, define once
relocation error Library not compiled with -fPIC Recompile library with -fPIC

Using pkg-config

Many libraries provide pkg-config files that specify correct flags automatically:

# Get compiler flags for a library
pkg-config --cflags openssl
# -I/usr/include/openssl

# Get linker flags
pkg-config --libs openssl
# -lssl -lcrypto

# Use in compilation (backticks or $())
gcc main.c $(pkg-config --cflags --libs openssl) -o program

# Or with backticks
gcc main.c `pkg-config --cflags --libs openssl` -o program

# List all available packages
pkg-config --list-all
Practice Questions

Task: Fix this compilation command:

gcc -lm main.c -o program
# Error: undefined reference to 'sqrt'
Show Solution
# Libraries must come AFTER the files that use them
gcc main.c -lm -o program

# The linker processes left to right:
# - First it sees -lm but nothing needs it yet
# - Then it sees main.c which needs sqrt
# - Too late! sqrt remains undefined

# Correct order: source files first, then libraries

Task: Your program uses libA, which internally uses libB. What is the correct link order?

Show Solution
# Rule: If X depends on Y, put X before Y

# main.c depends on libA
# libA depends on libB
# Therefore: main.c -> libA -> libB

gcc main.c -lA -lB -o program

# If you get it backwards:
gcc main.c -lB -lA -o program
# Error: undefined reference to functions from libB
# (because libA needs them but linker already passed libB)
05

Library Management

Professional C development requires understanding how to manage libraries on your system, inspect library contents, and handle library dependencies effectively.

Inspecting Libraries

# List symbols in a library
nm libmathlib.a
# T add          (T = text/code section, defined)
# T subtract
# U printf       (U = undefined, needs to be linked)

# Filter for defined functions only
nm -g --defined-only libmathlib.a

# View shared library dependencies
ldd ./program
# linux-vdso.so.1
# libmathlib.so => ./libmathlib.so
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6

# Get detailed library info
objdump -p libmathlib.so | head -30
readelf -d libmathlib.so

System Library Management

# Standard library locations
/lib                    # Essential system libraries
/usr/lib                # Standard libraries
/usr/local/lib          # Locally installed libraries

# Update library cache (after installing new libraries)
sudo ldconfig

# View library cache
ldconfig -p | grep math

# Add custom library path permanently
echo "/opt/mylibs" | sudo tee /etc/ld.so.conf.d/mylibs.conf
sudo ldconfig

Dynamic Loading (dlopen)

You can load libraries at runtime programmatically, useful for plugins and optional features:

// dynamic_load.c - Load library at runtime
#include <stdio.h>
#include <dlfcn.h>  // For dlopen, dlsym, dlclose

int main() {
    // Load the shared library
    void *handle = dlopen("./libmathlib.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "Error: %s\n", dlerror());
        return 1;
    }
    
    // Clear any existing errors
    dlerror();
    
    // Get function pointer
    int (*add)(int, int);
    add = (int (*)(int, int)) dlsym(handle, "add");
    
    char *error = dlerror();
    if (error) {
        fprintf(stderr, "Error: %s\n", error);
        dlclose(handle);
        return 1;
    }
    
    // Use the function
    printf("5 + 3 = %d\n", add(5, 3));
    
    // Close the library
    dlclose(handle);
    return 0;
}
# Compile with -ldl for dynamic loading functions
gcc dynamic_load.c -ldl -o dynamic_load

# Run the program
./dynamic_load
# 5 + 3 = 8

Creating a Library Project Structure

mylib/
├── include/
│   └── mylib/
│       └── mylib.h           # Public header
├── src/
│   ├── internal.h            # Private header
│   ├── feature1.c
│   ├── feature2.c
│   └── utils.c
├── tests/
│   └── test_mylib.c
├── examples/
│   └── demo.c
├── build/                    # Generated
│   ├── obj/
│   ├── libmylib.a
│   └── libmylib.so
├── Makefile
├── CMakeLists.txt            # Alternative build system
└── README.md
# Makefile for library project
CC = gcc
CFLAGS = -Wall -Wextra -fPIC -I include
LDFLAGS = 

SRCDIR = src
OBJDIR = build/obj
LIBDIR = build

SOURCES = $(wildcard $(SRCDIR)/*.c)
OBJECTS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SOURCES))

STATIC_LIB = $(LIBDIR)/libmylib.a
SHARED_LIB = $(LIBDIR)/libmylib.so

.PHONY: all static shared clean install

all: static shared

static: $(STATIC_LIB)

shared: $(SHARED_LIB)

$(STATIC_LIB): $(OBJECTS) | $(LIBDIR)
	ar rcs $@ $^

$(SHARED_LIB): $(OBJECTS) | $(LIBDIR)
	$(CC) -shared -o $@ $^

$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
	$(CC) $(CFLAGS) -c $< -o $@

$(OBJDIR) $(LIBDIR):
	mkdir -p $@

install: all
	cp $(STATIC_LIB) $(SHARED_LIB) /usr/local/lib/
	cp -r include/mylib /usr/local/include/
	ldconfig

clean:
	rm -rf build/
Best Practice: Always provide both static and shared versions of your library. Users can then choose based on their deployment needs. Include a pkg-config file for easy integration with other projects.
Practice Questions

Task: What command shows all function names exported by libcrypto.so?

Show Solution
# Show all symbols
nm -D /usr/lib/x86_64-linux-gnu/libcrypto.so

# Show only defined text (function) symbols
nm -D --defined-only /usr/lib/x86_64-linux-gnu/libcrypto.so | grep ' T '

# Alternative using objdump
objdump -T /usr/lib/x86_64-linux-gnu/libcrypto.so

# Count exported functions
nm -D --defined-only /usr/lib/x86_64-linux-gnu/libcrypto.so | grep ' T ' | wc -l

Task: You received a compiled program that crashes immediately. How do you check if all required libraries are available?

Show Solution
# Check library dependencies
ldd ./program

# Look for "not found" entries:
# libcustom.so => not found
# This indicates a missing library

# Find what package provides a library
apt-file search libcustom.so    # Debian/Ubuntu
yum provides */libcustom.so     # RHEL/CentOS

# If library exists but not found, add to path:
export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH

# Or install to system location and run ldconfig
sudo cp libcustom.so /usr/local/lib/
sudo ldconfig

Key Takeaways

Static Libraries (.a)

Archives of .o files linked at compile time; code copied into executable

Dynamic Libraries (.so)

Loaded at runtime; shared between programs; require -fPIC compilation

Link Order Matters

Libraries must come after the files that use them on command line

Library Paths

Use -L for compile-time, LD_LIBRARY_PATH or rpath for runtime

Library Versioning

Use MAJOR.MINOR.PATCH versioning with soname for compatibility

Useful Tools

ar (create), nm (symbols), ldd (dependencies), ldconfig (cache)

Knowledge Check

Quick Quiz

Test what you have learned about C libraries

1 What is the main difference between static and dynamic libraries?
2 Which command creates a static library?
3 Why is -fPIC required for shared libraries?
4 What causes "undefined reference" linker errors?
5 Which is the correct order for linking?
6 What does ldd program show?
Answer all questions to check your score