Skip to Content
Course content

128: Static vs Dynamic Libraries

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably heard the common wisdom that dynamic libraries are "better" because they save memory. The logic usually goes like this: "If ten different programs all use the same library, why put a copy of that code in ten different executables? Just put it in one .so or .dll and let them all share it."

On paper, that sounds like a win. In practice, it's a massive oversimplification that often leads developers to choose dynamic linking for the wrong reasons. If you're building a standalone tool, dynamic linking doesn't "save" you anything—it actually adds a layer of indirection that can slow down your function calls and, more annoyingly, creates a deployment nightmare where your app crashes because the user has the "wrong version" of a library installed. I've spent more hours than I care to admit debugging "DLL Hell" on client machines just because someone thought dynamic linking was the "efficient" choice.

The "Shared Memory" Magic Trick vs. Runtime Overhead

Let's look at a real example. Imagine you're writing a high-frequency trading application that uses a specialized library for Fast Fourier Transforms (FFT). If you link that FFT library statically, the linker takes the exact machine code for the functions you actually used and bakes them directly into your binary. When your code calls compute_fft(), the CPU jumps straight to a known memory address. It's fast, it's direct, and it's atomic.

Now, if you link it dynamically, your executable doesn't actually contain the FFT code. Instead, it contains a "stub" or a reference. When you launch the program, the OS loader has to find the .so or .dll on the disk, load it into memory, and resolve the addresses of the functions you need. This happens via a lookup table (called the PLT or Global Offset Table in Linux). Every single call to compute_fft() now requires an extra jump through this table. For most apps, this is negligible. For a low-latency trading app? It's a performance tax you don't want to pay.

// This looks the same in your code regardless of linking...
#include "fft_library.h"

int main() {
    double data[1024];
    // ... fill data ...
    compute_fft(data); // Static: Direct jump. Dynamic: Lookup table jump.
    return 0;
}

The "Binary Bloat" Myth vs. Linker Optimization

The other big misconception is that static libraries make your binaries "bloated." People assume that if a static library is 10MB, your executable grows by 10MB. This is fundamentally wrong because of how the linker works.

When you link dynamically, you are forced to load the entire library into memory, even if you only use one single function from it. The OS can't selectively load half of a .dll. However, when you link statically, the linker performs something akin to "tree shaking." It looks at your code, sees that you only called compute_fft() and ignored the other 500 functions in the library, and it only pulls the necessary object files into your final executable.

I've seen cases where a statically linked binary is actually smaller than the combined size of a dynamically linked binary and its required dependencies. So, if your goal is a portable, "single-file" distribution that doesn't break when the user updates their system libraries, static is your best friend.

  • Use Static (.a / .lib) when you want maximum performance, easier deployment, and you don't mind slightly longer link times during development.
  • Use Dynamic (.so / .dll) when you are building a plugin system, when the library is truly massive and used by dozens of different processes simultaneously, or when you need to be able to update the library without recompiling the main application.



📋 Practical Task

Building a Shared Math-Utility Library for a Multi-Binary Project

To truly understand the difference, you need to see the linker in action. Your task is to create a project consisting of one shared library and two separate executables that depend on it. This will simulate a real-world scenario where multiple tools share a common engine.

Requirements:

  • Create a library called libMathUtils. It should contain at least two functions: calculate_sphere_volume(double radius) and calculate_cylinder_volume(double radius, double height).
  • Create two separate C++ programs:
    • SphereTool: Only uses the sphere volume function.
    • CylinderTool: Only uses the cylinder volume function.
  • Compile libMathUtils as a dynamic/shared library (use -shared -fPIC if you are on Linux/macOS).
  • Link both SphereTool and CylinderTool against this shared library.
  • The Test: Run the programs. Then, try to move the .so or .dll file to a different folder and run the programs again. Observe the error message from the OS loader. This is the "Dynamic Linking" fragility we discussed.

Bonus Challenge: Re-compile the entire project using static linking (creating a .a or .lib file). Verify that the executables now run perfectly even after you delete the library file from the disk.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.