C Libraries — Static and Dynamic Library Creation and Linking
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
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.
Forgetting -L and -l flags:
-L.tells the linker to search the current directory.-lfoolinks against libfoo.a or libfoo.so. The order matters: link libraries after the object files that use them.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.
Library ordering in link command:
gcc main.o -lfoo -lbarworks 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.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
- What does -fPIC do and why is it necessary for shared libraries?
- How does LD_LIBRARY_PATH affect program execution?
- What is the difference between a static library (.a) and an archive file?
- How does dlopen/dlsym enable plugin architectures?
- 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 Next
Proceed to CMake to learn about cross-platform build configuration. Then explore File I/O for reading and writing files.