Skip to content

C Libraries — Static and Dynamic Library Creation and Linking

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about C Libraries. We cover key concepts, practical examples, and best practices to help you master this topic.

C libraries package compiled object code for reuse: static libraries (.a) are linked into the executable at build time, shared libraries (.so) are loaded at program start, and dynamically loaded libraries (dlopen) are loaded on demand during execution.

What You Will Learn

  • Creating static libraries with ar and ranlib
  • Building shared libraries with -shared -fPIC
  • Linking against libraries at compile time
  • Runtime linking with LD_LIBRARY_PATH
  • Dynamic loading with dlopen, dlsym, dlclose
  • Library versioning and soname
  • Library search paths

Why It Matters

Libraries are the foundation of code reuse in C. Every nontrivial program links against libc. Understanding how to create and use libraries lets you build reusable components, share code across projects, and distribute closed-source binaries without revealing source code. Durga Antivirus Pro uses a shared library (libscanengine.so) that is loaded by both the CLI scanner and the GUI, with the scanning engine updated independently.

Real-World Use

A company sells a face recognition library. They provide a shared library (libface.so) and a header file. Customers link against it without seeing the source code. When the company improves the algorithm, customers replace only the .so file and the application uses the new code without recompilation.

Learning Path

flowchart LR
  A[Multiple Files] --> B[Libraries\nYou are here]
  B --> C[CMake]
  style B fill:#f90,color:#fff

Creating a Static Library

// math_ops.h
#ifndef MATH_OPS_H
#define MATH_OPS_H

int add(int a, int b);
int multiply(int a, int b);
double power(double base, int exp);

#endif
// math_ops.c
#include "math_ops.h"

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

double power(double base, int exp) {
    double result = 1.0;
    for (int i = 0; i < exp; i++) result *= base;
    return result;
}

Build the static library:

gcc -c math_ops.c -o math_ops.o            # Compile to object file
ar rcs libmath_ops.a math_ops.o             # Create static library
ranlib libmath_ops.a                        # Index the library

Use the library:

gcc main.c -L. -lmath_ops -o program
./program

Using a Static Library

// main.c
#include <stdio.h>
#include "math_ops.h"

int main() {
    printf("2 + 3 = %d\n", add(2, 3));
    printf("2^10 = %.0f\n", power(2.0, 10));
    return 0;
}

Creating a Shared Library

// text_utils.c
#include "text_utils.h"
#include <string.h>
#include <ctype.h>

int count_words(const char *text) {
    int count = 0;
    int in_word = 0;
    while (*text) {
        if (isspace(*text)) {
            in_word = 0;
        } else if (!in_word) {
            in_word = 1;
            count++;
        }
        text++;
    }
    return count;
}

void to_uppercase(char *text) {
    while (*text) {
        *text = toupper(*text);
        text++;
    }
}

Build:

gcc -c -fPIC text_utils.c -o text_utils.o   # Compile with position-independent code
gcc -shared -o libtext_utils.so text_utils.o  # Create shared library

Use:

gcc main.c -L. -ltext_utils -o program
# Tell the runtime linker where to find the library
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./program

Library Versioning and Soname

# Create versioned shared library
gcc -c -fPIC -o libfoo.o libfoo.c
gcc -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0.0 libfoo.o

# Create symbolic links
ln -s libfoo.so.1.0.0 libfoo.so.1
ln -s libfoo.so.1 libfoo.so

# Link against the generic name
gcc main.c -L. -lfoo -o program

# Check which library version the program needs
ldd program

Dynamic Loading with dlopen

Load a library at runtime and call functions by name:

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int main() {
    // Load the shared library
    void *handle = dlopen("./libtext_utils.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "dlopen failed: %s\n", dlerror());
        return 1;
    }

    // Get function pointer by name
    int (*count_words)(const char*) = dlsym(handle, "count_words");
    if (!count_words) {
        fprintf(stderr, "dlsym failed: %s\n", dlerror());
        dlclose(handle);
        return 1;
    }

    // Call the function
    const char *text = "Hello world from dynamic loading";
    int count = count_words(text);
    printf("Word count: %d\n", count);

    // Unload the library
    dlclose(handle);
    return 0;
}

Compile with -ldl:

gcc -o plugin_loader plugin_loader.c -ldl

Library Search Paths

The linker and runtime loader search for libraries in this order:

# Compile-time: -L flag directories, then system paths
gcc main.c -L/usr/local/lib -lfoo

# Runtime: LD_LIBRARY_PATH, then /etc/ld.so.cache, then /lib, /usr/lib
export LD_LIBRARY_PATH=/opt/mylibs:$LD_LIBRARY_PATH

# System-wide: update cache with ldconfig
sudo cp libfoo.so.1 /usr/local/lib/
sudo ldconfig

Comparing Static vs Shared Libraries

Static:

  • Larger executable (library code copied in)
  • Independent deployment (no dependency on library)
  • Faster startup (no dynamic linking)
  • Library updates require re-linking

Shared:

  • Smaller executable (library code shared in memory)
  • Library can be updated without recompiling the program
  • Multiple programs share one copy in memory
  • Requires the library to be present at runtime

Common Mistakes

  1. Missing -fPIC when building shared libraries: Without position-independent code, the shared library may fail to load or corrupt memory. Always use -fPIC when compiling objects for shared libraries.

  2. Forgetting -L and -l flags: -L. tells the linker to search the current directory. -lfoo links against libfoo.a or libfoo.so. The order matters: link libraries after the object files that use them.

  3. Not setting LD_LIBRARY_PATH: If the shared library is not in a standard path, the runtime linker cannot find it. Set LD_LIBRARY_PATH or install the library to /usr/local/lib.

  4. Library ordering in link command: gcc main.o -lfoo -lbar works if main.o uses foo and foo uses bar. If main.o uses bar and bar uses foo, you need -lbar -lfoo. Some linkers support -Wl,--start-group ... -Wl,--end-group.

  5. Not versioning shared libraries: Without versioned sonames, a library update that changes the ABI silently breaks existing programs. Always use soname versioning for shared libraries.

Practice Questions

  1. What does -fPIC do and why is it necessary for shared libraries?
  2. How does LD_LIBRARY_PATH affect program execution?
  3. What is the difference between a static library (.a) and an archive file?
  4. How does dlopen/dlsym enable plugin architectures?
  5. Challenge: Create a plugin system where main.c loads implementation modules at runtime. Define a plugin interface that each .so must implement (e.g., void plugin_init(void), void plugin_run(void), void plugin_cleanup(void)). Write two plugins: one that prints "Hello" and one that prints "World". The main program loads all .so files from a directory and runs them.

Mini Project

Build a calculator with a plugin architecture:

  • Core library (libcalc_core.so): Basic operations (add, subtract, multiply, divide)
  • Plugin interface: A function that returns a struct with operation name, priority, and function pointer
  • Plugin examples: power, sqrt, factorial, modulo, gcd (each compiled as a separate .so)
  • Main program: loads all plugins from a plugins/ directory, presents a menu, and executes the chosen operation
  • Each plugin registers itself with a name and a function
  • Supports hot-reloading: detects new .so files in the plugins/ directory and loads them without restarting
  • Use: dlopen, dlsym, and proper error handling

FAQ

What is the difference between .a and .so files?

.a files are static libraries (archives of .o files). .so files are shared libraries (dynamically linked at runtime). .a makes larger executables, .so makes smaller ones.

How do I see which libraries a program needs?

Run ldd ./program. It shows all shared library dependencies and their paths. If a library is missing, it says 'not found'.

What is LD_PRELOAD?

An environment variable that forces loading a specific shared library before all others. Used for overriding functions in libc (e.g., malloc debugging, mocking) without recompiling.

Can I mix static and shared libraries?

Yes. A program can link some libraries statically and others dynamically. Use -static for all-static linking, or link individually: -lfoo (shared) /path/to/libbar.a (static).

What is rpath?

A path embedded in the executable at link time with -Wl,-rpath,. The runtime linker searches rpath directories before LD_LIBRARY_PATH. Useful for deploying programs with private libraries.

What is Next

Proceed to CMake to learn about cross-platform build configuration. Then explore File I/O for reading and writing files.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse C