Skip to main content

Overview

Dynamic libraries (also called shared libraries) are collections of compiled code that are loaded into memory at runtime rather than compile time. They allow multiple programs to share the same library code in memory, reducing disk space and memory usage.
Dynamic libraries are loaded at runtime, which means you can update the library without recompiling programs that use it.

Dynamic vs Static Libraries

Static Libraries (.a)

  • Linked at compile time
  • Code copied into executable
  • Larger binary size
  • No runtime dependencies
  • Faster startup time

Dynamic Libraries (.so)

  • Linked at runtime
  • Code shared in memory
  • Smaller binary size
  • Requires library at runtime
  • Updateable without recompiling

What is a Dynamic Library?

A dynamic library (.so file - Shared Object) contains compiled code that can be loaded into a program’s memory space at runtime. The .so extension is the Linux equivalent of Windows .dll files.

Advantages

  • Memory efficiency: Multiple programs share one copy in memory
  • Disk space savings: Library code not duplicated in each executable
  • Easy updates: Update library without recompiling programs
  • Plugin architecture: Load functionality dynamically

Disadvantages

  • Runtime dependencies: Library must be present on the system
  • Version conflicts: “DLL hell” if incompatible versions exist
  • Slightly slower startup: Time needed to load and link at runtime

Creating a Dynamic Library

Library Interface

The header file defines the functions available in our library:
main.h
#ifndef MAIN_H
#define MAIN_H

int _putchar(char c);
int _islower(int c);
int _isalpha(int c);
int _abs(int n);
int _isupper(int c);
int _isdigit(int c);
int _strlen(char *s);
void _puts(char *s);
char *_strcpy(char *dest, char *src);
int _atoi(char *s);
char *_strcat(char *dest, char *src);
char *_strncat(char *dest, char *src, int n);
char *_strncpy(char *dest, char *src, int n);
int _strcmp(char *s1, char *s2);
char *_memset(char *s, char b, unsigned int n);
char *_memcpy(char *dest, char *src, unsigned int n);
char *_strchr(char *s, char c);
unsigned int _strspn(char *s, char *accept);
char *_strpbrk(char *s, char *accept);
char *_strstr(char *haystack, char *needle);

#endif /* MAIN_H */

Build Process

1

Compile with Position Independent Code

Compile source files with the -fPIC flag:
gcc *.c -c -fPIC
What is -fPIC?Position Independent Code can be loaded at any memory address, which is essential for shared libraries that might be loaded at different addresses in different programs.
-fPIC stands for “Position Independent Code” - it generates code that doesn’t depend on being loaded at a specific memory address.
2

Create the Shared Library

Link object files into a shared library:
gcc *.o -shared -o liball.so
Flags explained:
  • -shared - Create a shared library instead of an executable
  • -o liball.so - Output file name

Automated Build Script

Create a script to automate the build process:
1-create_dynamic_lib.sh
#!/bin/bash
gcc *.c -c -fPIC
gcc *.o -shared -o liball.so
Dynamic libraries in Linux use the .so extension (Shared Object), equivalent to .dylib on macOS and .dll on Windows.

Using Dynamic Libraries

Method 1: Compile-Time Linking

gcc -L. -lmy main.c -o program
Flags explained:
  • -L. - Look for libraries in current directory
  • -lmy - Link against libmy.so

Method 2: Runtime Library Path

Set the library path before running:
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./program
LD_LIBRARY_PATH tells the dynamic linker where to find shared libraries at runtime.

Method 3: System Library Directory

Copy the library to a system directory:
sudo cp libmy.so /usr/local/lib/
sudo ldconfig
ldconfig updates the shared library cache.

Example Usage

Sample Program

0-main.c
#include "main.h"
#include <stdio.h>
#include <stdlib.h>

/**
 * main - check the code
 *
 * Return: Always EXIT_SUCCESS.
 */
int main(void)
{
    printf("%d\n", _strlen("My Dyn Lib"));
    return (EXIT_SUCCESS);
}

Compilation and Execution

gcc -L. -ldynamic 0-main.c -o test

Examining Dynamic Libraries

List Library Dependencies

See what libraries an executable depends on:
ldd program
Output example:
linux-vdso.so.1 (0x00007ffd8d3e7000)
libmy.so => ./libmy.so (0x00007f8b9c3e5000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8b9c1e3000)

Display Symbol Table

nm -D libmy.so
The -D flag shows dynamic symbols.

Check Exported Symbols

objdump -T libmy.so
Displays the dynamic symbol table.

Environment Variables

Specifies additional directories to search for libraries:
export LD_LIBRARY_PATH=/path/to/libs:$LD_LIBRARY_PATH
Use cases:
  • Testing libraries before installation
  • Using libraries in non-standard locations
  • Development and debugging
Don’t use LD_LIBRARY_PATH in production - install libraries to system directories instead.
Forces specific libraries to be loaded before others:
LD_PRELOAD=/path/to/library.so ./program
Use cases:
  • Function interception
  • Debugging and profiling
  • Overriding system functions
Enables dynamic linker debugging:
LD_DEBUG=libs ./program
LD_DEBUG=symbols ./program
LD_DEBUG=all ./program
Helps diagnose library loading issues.

Advanced Topics

Symbol Visibility

Control which symbols are exported:
gcc -fPIC -fvisibility=hidden *.c -shared -o libmy.so
Then mark specific functions as visible:
__attribute__((visibility("default")))
int public_function(void);

Versioning

Create versioned libraries:
gcc -shared -Wl,-soname,libmy.so.1 -o libmy.so.1.0.0 *.o
ln -s libmy.so.1.0.0 libmy.so.1
ln -s libmy.so.1 libmy.so

Loading Libraries Programmatically

Use dlopen() to load libraries at runtime:
#include <dlfcn.h>

void *handle = dlopen("./libmy.so", RTLD_LAZY);
int (*func)(char *) = dlsym(handle, "_strlen");
int result = func("Hello");
dlclose(handle);

Common Issues

Error message:
./program: error while loading shared libraries: libmy.so: cannot open shared object file
Solutions:
  1. Set LD_LIBRARY_PATH
  2. Copy library to /usr/local/lib and run ldconfig
  3. Use -rpath during compilation:
    gcc -Wl,-rpath,/path/to/lib -L/path/to/lib -lmy main.c
    
Error message:
wrong ELF class: ELFCLASS32
Cause: Mixing 32-bit and 64-bit libraries/programsSolution: Ensure all components are compiled for the same architecture:
file program
file libmy.so
Cause: Library doesn’t contain required symbols or wrong library versionDebug:
nm -D libmy.so | grep symbol_name
ldd program

Best Practices

Naming

Use lib prefix and .so extension:
  • libutils.so
  • libmath.so

Versioning

Version your libraries:
  • libmy.so.1.0.0 (full version)
  • libmy.so.1 (major version)
  • libmy.so (development link)

ABI Stability

Maintain binary compatibility:
  • Don’t change function signatures
  • Don’t remove public symbols
  • Use version scripts

Installation

Install to standard locations:
  • /usr/lib for system libraries
  • /usr/local/lib for custom libraries
  • Run ldconfig after installation

Comparison with Static Libraries

FeatureStatic LibraryDynamic Library
Extension.a.so
Link timeCompile timeRuntime
Binary sizeLargerSmaller
Memory usageHigherLower (shared)
Update processRecompile programsReplace library file
DependenciesNoneLibrary must be present
PortabilityBetterRequires library on target system

Build docs developers (and LLMs) love