A library file is a bundled collection of object files, packaged with an index for fast symbol lookup. Libraries hold reusable subroutines that many programs need: math functions, I/O routines, string manipulation, networking, GUI primitives. Used during linking to pull those subroutines into a program without recompiling them every time.

The canonical example is the C standard library (libc), which provides printf, malloc, strlen, fopen, and the rest of the standard C functions every C program uses.

Two main flavors

Static libraries

Suffix: .a (Unix), .lib (Windows).

The Linker copies the needed members into the final executable. Once linked, the library code is part of the executable, so the library file isn’t needed to run the program.

Pros:

  • Self-contained executable, no runtime dependencies.
  • No risk of “missing library” errors at run time.
  • Function calls are slightly faster (no indirection through dynamic resolution).

Cons:

  • Larger executables (each program includes its own copy of common library code).
  • Library updates require relinking every program that uses them.
  • Wastes memory when many programs run simultaneously and each holds its own copy.

Shared / dynamic libraries

Suffix: .so (Linux), .dll (Windows), .dylib (macOS).

The linker only records that the library is required. The actual library code is loaded at runtime by the Loader (or the dynamic linker that the loader invokes).

Pros:

  • Smaller executables.
  • Multiple programs can share one in-memory copy of the library.
  • Library updates take effect for all programs without recompilation (good for security patches).

Cons:

  • “Missing library” errors at run time if the library isn’t installed correctly.
  • Slight indirection overhead per call.
  • “DLL hell”: version conflicts when programs need different versions of the same library.

How linking with libraries works

When you compile a program that uses printf:

  1. The compiler emits a call to printf in the object file, marked as an external reference.
  2. The linker is told to use libc (-lc on Unix command lines).
  3. The linker searches libc for printf, finds it, and either:
    • Static: copies printf’s machine code into the executable.
    • Dynamic: writes a stub that defers to the dynamic linker at runtime.

For dynamic linking, the executable contains a list of required libraries. At launch, the Loader reads this list, locates each library on disk (using the system’s library search path), maps it into the program’s address space, and resolves the stubs.

Library structure

A .a file (static library on Unix) is essentially an archive (think .zip) of object files, with a global symbol index for fast lookup. You can extract individual .o files from it:

ar -t libfoo.a            # list members
ar -x libfoo.a member.o   # extract

A .so file (shared library) is a single linkable object with extra metadata about its exported symbols. Tools like nm and objdump work on .so files just like on .o files.

Examples

Common Linux libraries:

  • libc.so.6 — C standard library (printf, malloc, file I/O).
  • libm.so.6 — math library (sin, cos, sqrt).
  • libpthread.so.0 — POSIX threads.
  • libstdc++.so.6 — C++ standard library.
  • libssl.so — SSL/TLS encryption.

Run ldd <executable> to see what an actual binary pulls in. A trivial CLI tool like /bin/ls on a modern Linux pulls in a handful (libc, libpthread, the dynamic-linker, plus selinux/cap/pcre helpers); something graphical like Firefox or VS Code pulls in dozens, since each toolkit (GTK/Qt), codec, and protocol library brings its own.

Why libraries matter

Without libraries, every program would need its own copy of every utility function. The original Unix philosophy was small composable programs sharing libraries, and modern systems still lean on this for stability and disk/memory efficiency.

For embedded systems with no dynamic loader, static libraries are the only option. Everything gets linked into a single firmware image.