Skip to main content
โšก Calmops

Creating C Libraries: Static and Dynamic Libraries Complete Guide

Introduction

Creating libraries is a fundamental skill for C programmers that enables code reuse, modularity, and efficient distribution of compiled code. Libraries allow you to package compiled functions that can be linked into multiple programs without redistributing source code. Understanding how to create and use both static and dynamic libraries is essential for building professional-quality C applications and sharing code across projects.

This comprehensive guide covers the complete process of creating, using, and managing both static and dynamic libraries in Linux environments. Whether you’re building a shared utility library, creating a software component for distribution, or simply organizing your own code, this guide provides the knowledge needed to work effectively with C libraries.

Understanding Library Types

Static Libraries

Static libraries are archive files containing compiled object code that gets copied directly into the executable at compile time. When you link against a static library, the linker extracts the necessary object code and embeds it into your final executable. The resulting program is self-contained and doesn’t require the library to be present at runtime.

Static libraries offer several advantages. They create standalone executables that are easy to deploy, since there’s no dependency on external library files. They ensure consistent behavior across systems, as the exact library version is baked into your executable. They can also improve performance by enabling more aggressive compiler optimizations.

However, static libraries have drawbacks. They increase executable size since code is duplicated in every program. Updates to the library require recompiling all programs that use it. They also consume more memory when multiple programs using the same library are running, as each has its own copy of the code.

Dynamic Libraries

Dynamic libraries (also called shared libraries) are loaded into memory at runtime, either when the program starts or when functions are first called. The library code exists as a separate file that multiple programs can share, reducing memory usage and enabling library updates without recompiling dependent programs.

Dynamic libraries provide significant benefits. They reduce executable size since library code isn’t embedded. They allow updating library code without recompiling programs. Multiple programs share a single copy of the library in memory, improving resource efficiency. They enable plugins and modules that load at runtime.

The trade-offs include potential compatibility issues if library versions change, the need to ensure libraries are present at runtime, and slightly more complex deployment.

Platform-Specific Naming

Different platforms use different file extensions and conventions for libraries:

  • Linux: Static libraries use .a (archive), dynamic libraries use .so (shared object)
  • macOS: Static libraries use .a, dynamic libraries use .dylib
  • Windows: Static libraries use .lib, dynamic libraries use .dll

This guide focuses on Linux conventions, which are also largely applicable to macOS.

Creating a Static Library

Step 1: Create Source Files

First, create the source files for your library functions. Here’s a simple example:

/* math_utils.h */
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int add(int a, int b);
int multiply(int a, int b);
double square(double x);

#endif

/* math_utils.c */
#include "math_utils.h"

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

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

double square(double x) {
    return x * x;
}

Step 2: Compile Source to Object Files

Compile your source files into object files using the -c flag, which prevents linking:

gcc -c -fPIC -o math_utils.o math_utils.c

The -fPIC flag generates position-independent code, which is good practice even for static libraries as it allows flexibility if you later want to convert to a shared library. The -c flag tells gcc to compile but not link.

Step 3: Create the Static Library

Use the ar (archive) command to create the static library:

ar rcs libmath_utils.a math_utils.o

The flags mean:

  • r: Replace or add files
  • c: Create the archive if it doesn’t exist
  • s: Create an index (optional but recommended)

You can verify the library contents:

ar -t libmath_utils.a
# Output: math_utils.o

nm libmath_utils.a
# Output: symbols in the library

Step 4: Using the Static Library

Create a program that uses the library:

/* main.c */
#include <stdio.h>
#include "math_utils.h"

int main() {
    printf("5 + 3 = %d\n", add(5, 3));
    printf("4 * 7 = %d\n", multiply(4, 7));
    printf("5.0 squared = %.2f\n", square(5.0));
    return 0;
}

Compile and link the library:

gcc -o program main.c -L. -lmath_utils

The flags mean:

  • -L.: Add the current directory to the library search path
  • -lmath_utils: Link against libmath_utils.a (note: lib prefix and .a suffix are assumed)

Or link explicitly:

gcc -o program main.c libmath_utils.a

Run the program:

./program
# Output:
# 5 + 3 = 8
# 4 * 7 = 28
# 5.0 squared = 25.00

Creating a Dynamic Library

Step 1: Compile Source with Position-Independent Code

For dynamic libraries, position-independent code is required:

gcc -c -fPIC -o math_utils.o math_utils.c

The -fPIC flag is essential for shared libraries, generating code that can be loaded at any memory address.

Step 2: Create the Shared Library

Create the shared library with appropriate flags:

gcc -shared -o libmath_utils.so math_utils.o

The -shared flag tells the compiler to create a shared library rather than an executable.

Step 3: Using the Dynamic Library

Link against the shared library:

gcc -o program main.c -L. -lmath_utils

At runtime, the program needs to find the library. You can set the search path:

# Using rpath (embedded in executable)
gcc -o program main.c -L. -lmath_utils -Wl,-rpath,'$ORIGIN'

# Using LD_LIBRARY_PATH
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./program

# Installing to system library path
sudo cp libmath_utils.so /usr/local/lib/
sudo ldconfig

Library Versioning

Dynamic libraries should be versioned:

# Create versioned library
gcc -shared -fPIC -o libmath_utils.so.1.0.0 math_utils.o

# Create symlinks
ln -s libmath_utils.so.1.0.0 libmath_utils.so.1
ln -s libmath_utils.so.1 libmath_utils.so

Version naming follows the convention libname.so.major.minor.patch.

Adding More Object Files

To add object files to an existing static library:

ar rcs libmath_utils.a math_utils.o math_advanced.o

To list contents:

ar -t libmath_utils.a

To extract files:

ar x libmath_utils.a

Managing Library Search Paths

At Compile Time

  • -L<path>: Add directory to library search path during linking
  • -l<name>: Link against lib.so or lib.a

At Runtime (Dynamic Libraries)

  • Use LD_LIBRARY_PATH environment variable
  • Use -Wl,-rpath to embed path in executable
  • Install to standard paths like /usr/lib

Check library dependencies:

ldd ./program
# Output:
# linux-vdso.so.1 (0x00007fff...)
# libmath_utils.so => ./libmath_utils.so (0x...)
# libc.so.6 => /lib64/libc.so.6 (0x...)

Summary and Best Practices

When to use static libraries:

  • Building standalone executables
  • Deploying to systems where you can’t install libraries
  • When library code rarely changes
  • Performance-critical applications

When to use dynamic libraries:

  • Multiple programs sharing the same code
  • When library updates are frequent
  • Plugin systems requiring runtime loading
  • Reducing memory footprint

Best practices:

  • Always use meaningful library names with lib prefix
  • Version dynamic libraries properly
  • Use -Wall -Wextra during compilation
  • Create appropriate header files for your library
  • Document your library API thoroughly
  • Consider using pkg-config for build systems

Static libraries make distribution and deployment easier for small projects, while dynamic libraries are preferred for shared code and reducing executable size. Understanding both enables you to choose the right approach for each situation.

Comments