Skip to content
Home » Embedded Systems » Embedded C » Using Static and Dynamic Library Files in C

Using Static and Dynamic Library Files in C

Static and Dynamic Library Files in C featured image with dark blue background, C FOUNDATIONS badge, .a .so icon in teal circle, and Building .a and .so Libraries subtitle by nerdyelectronics.com
Embedded Systems Learning Path
Part 30 of 129View Full Path →

KEY TAKEAWAYS

  • Static libraries (.a/.lib) are linked at compile time and become part of the executable
  • Dynamic/shared libraries (.so/.dll) are loaded at runtime and shared between programs
  • Static linking produces larger but self-contained executables; dynamic linking saves disk space and memory
  • Create static libraries with ar and dynamic libraries with gcc -shared

What are Libraries?

A library in C is a collection of pre-compiled functions and code that you can reuse in your programs. Instead of writing everything from scratch, you link against libraries that provide common functionality.For example, when you use printf(), you are using a function from the C standard library (libc). You did not write printf yourself. It was compiled into a library file, and the linker connects your program to it.Libraries come in two types:
  • Static libraries (.a on Linux/macOS, .lib on Windows)
  • Dynamic (shared) libraries (.so on Linux, .dylib on macOS, .dll on Windows)

Static Libraries

A static library is an archive of compiled object files (.o files). When you link your program with a static library, the linker copies the required code from the library directly into your final executable.
   Source Files          Static Library           Executable
   +--------+           +----------+            +-----------+
   | main.c | --compile-->         |            |           |
   +--------+           | libmath.a|--link-->   | my_program|
   | app.c  | --compile-->         |            | (contains |
   +--------+           +----------+            | all code) |
                                                +-----------+

Advantages of Static Libraries

  • Self-contained: The executable contains everything it needs. No external dependencies at runtime.
  • Faster execution: No overhead from loading external libraries at startup.
  • Deterministic: The behavior does not change if system libraries are updated.
  • Ideal for embedded: Microcontrollers typically use static linking because there is no OS to manage shared libraries.

Disadvantages of Static Libraries

  • Larger executable size: Every program includes its own copy of the library code.
  • No shared memory: If 5 programs use the same library, the code exists 5 times in memory.
  • Recompilation needed: If the library is updated, every program using it must be relinked.

Creating a Static Library

Let us create a simple math library:mymath.h:
#ifndef MYMATH_H
#define MYMATH_H

int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
float divide(int a, int b);

#endif
mymath.c:
#include "mymath.h"

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

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

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

float divide(int a, int b) {
    if (b == 0) return 0.0f;
    return (float)a / (float)b;
}
Step 1: Compile the source to an object file:
gcc -c mymath.c -o mymath.o
Step 2: Create the static library using ar (archiver):
ar rcs libmymath.a mymath.o
The flags mean:
  • r – Insert files into the archive (replace if exists)
  • c – Create the archive if it does not exist
  • s – Write an index (for faster linking)
Step 3: Use the library in your program:main.c:
#include <stdio.h>
#include "mymath.h"

int main() {
    printf("5 + 3 = %d\n", add(5, 3));
    printf("5 - 3 = %d\n", subtract(5, 3));
    printf("5 * 3 = %d\n", multiply(5, 3));
    printf("5 / 3 = %.2f\n", divide(5, 3));
    return 0;
}
Compile and link:
gcc main.c -L. -lmymath -o calculator
The flags mean:
  • -L. – Look for libraries in the current directory
  • -lmymath – Link with libmymath.a (the “lib” prefix and “.a” suffix are added automatically)

Dynamic (Shared) Libraries

A dynamic library (also called a shared library) is loaded at runtime rather than being copied into the executable at compile time. The executable contains only a reference to the library, and the operating system loads the library into memory when the program starts.
   Executable              Shared Library (in memory)
   +-----------+          +------------+
   | my_program|---ref--->| libmath.so |
   | (small)   |          | (loaded    |
   +-----------+          |  at runtime)|
                          +------------+
                               ^
   +-----------+               |
   | other_prog|---ref---------+  (shares same copy)
   +-----------+

Advantages of Dynamic Libraries

  • Smaller executables: The library code is not copied into the executable.
  • Shared memory: Multiple programs can share a single copy of the library in memory.
  • Update without recompilation: You can update the library without relinking the programs that use it.
  • Plugin systems: Load and unload libraries dynamically at runtime.

Disadvantages of Dynamic Libraries

  • Runtime dependency: The library must be present on the system. Missing library = program fails to start.
  • Version conflicts: Different programs may need different versions of the same library (“DLL hell”).
  • Slightly slower startup: The OS must locate and load the library at program start.
  • Not suitable for bare-metal embedded: Requires an OS with a dynamic linker.

Creating a Dynamic Library

Using the same mymath source files:Step 1: Compile with position-independent code (PIC):
gcc -c -fPIC mymath.c -o mymath.o
The -fPIC flag generates code that can be loaded at any memory address, which is required for shared libraries.Step 2: Create the shared library:
gcc -shared -o libmymath.so mymath.o
Step 3: Compile and link your program:
gcc main.c -L. -lmymath -o calculator
Step 4: Run the program (Linux):
# Tell the loader where to find the library
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./calculator
Or install the library system-wide:
sudo cp libmymath.so /usr/local/lib/
sudo ldconfig

Loading Dynamic Libraries at Runtime

You can also load shared libraries programmatically using dlopen():
#include <stdio.h>
#include <dlfcn.h>

int main() {
    // Load the library
    void *handle = dlopen("./libmymath.so", RTLD_LAZY);
    if (!handle) {
        printf("Error: %s\n", dlerror());
        return 1;
    }

    // Get function pointer
    int (*add_func)(int, int) = dlsym(handle, "add");
    if (!add_func) {
        printf("Error: %s\n", dlerror());
        dlclose(handle);
        return 1;
    }

    // Use the function
    printf("5 + 3 = %d\n", add_func(5, 3));

    // Unload the library
    dlclose(handle);
    return 0;
}
Compile with:
gcc main.c -ldl -o calculator
This pattern is used in plugin systems where you do not know at compile time which libraries will be loaded.

Static vs Dynamic: Comparison

FeatureStatic Library (.a / .lib)Dynamic Library (.so / .dll)
Linking timeCompile timeRuntime
Executable sizeLarger (includes library code)Smaller (references only)
Memory usageEach program has its own copyShared between programs
DependenciesNone (self-contained)Library must exist at runtime
UpdatesMust relink all programsReplace library file only
PerformanceSlightly faster (no runtime loading)Slightly slower startup
Embedded systemsStandard choice (bare-metal)Used on Linux-based systems (RPi, etc.)
File extension (Linux).a.so
File extension (Windows).lib.dll

When to Use Which

Use static libraries when:
  • Building for bare-metal embedded systems (no OS)
  • You need a fully self-contained executable
  • Deterministic behavior is critical (no surprise library updates)
  • Distributing a standalone application
Use dynamic libraries when:
  • Building for Linux-based embedded systems (Raspberry Pi, BeagleBone)
  • Multiple programs share the same library
  • You want to update library code without rebuilding everything
  • Building a plugin system where modules are loaded on demand

Libraries in Embedded Build Systems

In a typical Makefile for an embedded project, you might use both:
# Static library for your own modules
STATIC_LIBS = libdrivers.a libprotocol.a

# Dynamic libraries (Linux-based embedded only)
DYNAMIC_LIBS = -lpthread -lssl

# Build static library
libdrivers.a: uart.o spi.o gpio.o
	ar rcs $@ $^

# Link everything
firmware: main.o $(STATIC_LIBS)
	gcc main.o -L. -ldrivers -lprotocol $(DYNAMIC_LIBS) -o firmware

Useful Commands

# List contents of a static library
ar -t libmymath.a

# List symbols in a library
nm libmymath.a

# Check which dynamic libraries a program needs
ldd ./calculator

# Check symbols in a shared library
nm -D libmymath.so

Summary

Libraries let you organize, reuse, and share compiled code efficiently:
  • Static libraries (.a) are archives of object files linked at compile time. The code is copied into your executable. They are the standard choice for bare-metal embedded systems.
  • Dynamic libraries (.so/.dll) are loaded at runtime by the operating system. They save memory when multiple programs share the same code. They are used on Linux-based embedded platforms.
  • Use ar to create static libraries and gcc -shared to create dynamic libraries.
  • Link with -L (library path) and -l (library name).
Understanding both types is essential for building well-organized C projects, whether you are working on a small microcontroller firmware or a large Linux-based embedded application.

Leave a Reply

Your email address will not be published. Required fields are marked *