Static library vs dynamic (shared) library in detail

Introduction

When building software, choosing between static and dynamic libraries impacts performance, binary size, and maintainability. Static libraries bundle code directly into the executable, making deployment easier but increasing file size. Dynamic libraries, on the other hand, are loaded at runtime, reducing binary size but introducing potential dependency issues.

This post explores the key differences between static and dynamic libraries, how they are linked, how dynamic libraries are located at runtime, and important considerations like /MT vs. /MD in MSVC and PIC (Position Independent Code).

Static library

A static library (e.g., .a on Linux or .lib on Windows) is essentially a collection of object files (.o or .obj) that are archived together.

When you link a static library into an executable or another library, the linker extracts the required object files and includes them directly in the final binary.

Dynamic library

A dynamic library (e.g., .so on Linux or .dll on Windows) is a shared library that is loaded at runtime. The code is not included in the final binary; instead, the binary contains references to the dynamic library.

On Windows, a .lib file accompanying a .dll serves as an import library used during the development process. It contains stubs for the functions and symbols exported by the DLL, allowing the linker to resolve references to those functions when building an application.

Find Dynamic library at runtime

On Linux, dynamic libraries (.so files) are located using a specific search order:

  • the system checks the RPATH or RUNPATH embedded in the executable,
  • it looks at the LD_LIBRARY_PATH environment variable,
  • default system paths like /lib, /usr/lib, and /usr/local/lib.

Using RPATH or RUNPATH is recommended for portability, while LD_LIBRARY_PATH is useful for development but should be avoided in production.

On Windows, dynamic libraries (.dll files) are located by

  • first checking the directory where the executable resides,
  • the system searches system directories like System32 or SysWOW64, Windows directory,
  • Finally, it checks the current working directory and directories listed in the PATH environment variable.

For portability, it’s best to place DLLs in the same directory as the executable, while modifying the PATH should be done sparingly.

The above methods are for implicitly linking, we can explicitly use functions like LoadLibrary (Windows) or dlopen (Linux) to load libraries dynamically.

/MT vs /MD

In MS Visual C++, the /MT flag is a compiler option used to link the multithreaded, static version of the C runtime library. This means the runtime library code is embedded directly into the executable, resulting in a larger binary but eliminating the need for external runtime DLLs. This approach is beneficial for creating self-contained applications that do not rely on external dependencies, making it ideal for distributing software to systems where the required runtime libraries might not be installed.

On the other hand, the /MD flag dynamically links the multithreaded version of the runtime library, relying on external DLLs like msvcr140.dll. This reduces the size of the executable but requires the target system to have the appropriate Microsoft Visual C++ Redistributable installed.

Mixing /MT (static CRT) and /MD (dynamic CRT) compiled objects in the same application can cause linker errors, runtime crashes, or memory mismanagement because each CRT has its own heap manager and global state, leading to incompatibilities. To avoid these issues, all parts of your project — including your main application and all linked static libraries — must be compiled with the same runtime library setting (/MT, /MTd, /MD, or /MDd). Prefer /MD for most projects to reduce conflicts and improve compatibility.

I created an example where project app was linked to dynamic library ashared and static library astatic.

  • When app was compiled in debug but it was linked to release version of a static library, astatic. I got this error:
astatic.lib(astatic.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '0' doesn't match value
'2' in app.obj
astatic.lib(astatic.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MD_DynamicRelease' doesn't mat
ch value 'MDd_DynamicDebug' in app.obj
  • The compilation of ashared with different flags (/MT, /MTd, /MD, or /MDd) led to successful compilation of app.

  • The compilation of astatic must be the same flag as compilation of app to get a successful build. Otherwise, I’d get errors like:

astatic.lib(astatic.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MD_DynamicRelease' doesn't match value 'MT_StaticRelease' in app.obj [...\app\build\myapp.vcxproj]
msvcprt.lib(MSVCP140.dll) : error LNK2005: "public: int __cdecl std::ios_base::flags(void)const " (?flags@ios_base@std@@QEBAHXZ) already defined in libcpmt.lib(
locale.obj) [...\app\build\myapp.vcxproj]

.lib beside .dll

On Windows, a .lib file accompanying a .dll serves as an import library used during the development process. It contains stubs for the functions and symbols exported by the DLL, allowing the linker to resolve references to those functions when building an application. At runtime, the .lib file is not needed, only the .dll file must be present and accessible to the executable.

PIC

POSITION INDEPENDENT CODE (PIC) is a property of compiled code that allows it to execute correctly regardless of where it is loaded into memory.

  1. Dynamic (Shared) Libraries

    • PIC is required for shared libraries because they are loaded at runtime and can be mapped to different memory locations by the operating system.
    • The use of PIC ensures that all addresses are computed relative to a known location rather than absolute addresses, making relocation easier.
  2. Static Libraries

    • PIC is not required for static libraries because they are linked into the final executable at compile time, meaning their memory addresses are resolved before execution.
    • However, if a static library is later used to build a shared library, it must have been compiled with -fPIC to ensure compatibility.

PIC introduces slight overhead because it often requires additional indirections.

Pros and Cons

Static library

✔ Faster execution (no runtime linking overhead)

✔ No external dependencies at runtime

✖ Larger binary size

✖ Requires rebuilding if the library is updated

Dynamic library

✔ Smaller executable size

✔ Easier updates (just replace the .so/.dll file)

✖ Slight runtime overhead due to dynamic linking

Tags ➡ C++ CMake

Subscribe

I notify you of my new posts

Latest Posts

Comments

0 comment