Skip to Content
Course content

76: Static Libraries vs Dynamic Libraries

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

I've spent a lot of time over the years hunting down "undefined reference" errors and "library not found" crashes. Most of these issues stem from a fundamental misunderstanding of how code actually gets bundled together. We've been compiling files individually or linking them directly, but in a real project, you can't just pass twenty .c files to the compiler every time. You need libraries.

Packing everything into one suitcase

Let's start with a practical scenario. I've written a small geometry utility that handles some circle calculations. I want to reuse this in multiple different programs without having to copy-paste the source code.

// geometry.c
#include <math.h>
double calculate_circle_area(double radius) {
    return 3.14159 * radius * radius;
}

Normally, I'd just compile this with my main program. But instead, I'm going to turn it into a static library. First, I'll compile it to an object file, and then I'll use the ar (archiver) tool to bundle it into a .a file.

gcc -c geometry.c -o geometry.o
ar rcs libgeometry.a geometry.o

Now I have libgeometry.a. When I link this to my main.c, the linker literally copies the machine code from the library and pastes it directly into my final executable. I'll try it out:

gcc main.c -L. -lgeometry -lm -o geo_app

It works. But here is the catch: if I check the file size of geo_app, it's larger than if I had a minimal program. Why? Because the calculate_circle_area code is now physically inside the binary. If I have ten different apps using this library, I have ten copies of that code taking up space on the disk and in RAM. Even worse, if I find a bug in the area calculation and update the library, I have to recompile and re-link every single one of those ten apps to apply the fix. That's a maintenance nightmare.

Sharing the load at runtime

This is where dynamic libraries (Shared Objects) come in. Instead of copying the code into the binary, a dynamic library tells the executable: "I'm not giving you the code now, but I promise it'll be available at a specific address when you actually run."

I'll try to convert my geometry utility into a shared library. I have to add a special flag here: -fPIC. This stands for Position Independent Code, which is necessary because the library could be loaded anywhere in memory.

gcc -fPIC -c geometry.c -o geometry.o
gcc -shared -o libgeometry.so geometry.o

Now I link my app against the .so file instead of the .a file:

gcc main.c -L. -lgeometry -lm -o geo_app_dynamic

The compilation finishes instantly. The binary is smaller. I'm feeling good. But when I try to run it... ./geo_app_dynamic... it crashes immediately with an error: error while loading shared libraries: libgeometry.so: cannot open shared object file.

The "Where is my library?" problem

This is the classic dynamic linking trap. When we used the static library, the code was already inside the binary. With a dynamic library, the OS loader has to find the .so file at the moment the program starts. By default, the loader looks in standard system paths like /usr/lib, but it doesn't look in the current folder.

I have two choices. I could move the library to a system folder (which requires sudo and is messy for development), or I can tell the loader where to look using an environment variable. Let's try the latter:

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./geo_app_dynamic

Now it runs perfectly. Here is the "magic" moment: I'm going to change the math in geometry.c to be more precise, recompile only the libgeometry.so file, and then run ./geo_app_dynamic again without recompiling the main app. The output changes instantly. The app is picking up the updated logic from the shared library on the fly.

Choosing your weapon

So, which one should you use? I usually follow this rule of thumb:

  • Static (.a): Use these when you want a "portable" binary that has zero dependencies. You ship one file, and it just works, regardless of what libraries are installed on the user's system. It's great for small utilities.
  • Dynamic (.so): Use these for large frameworks or when multiple programs share the same logic. It saves memory and allows you to push updates to the library without forcing every dependent application to be rebuilt.



📋 Practical Task

Building a Shared String-Utility Toolkit

Your goal is to create a shared library that provides custom string manipulation functions and link it to a program. This will test your ability to handle Position Independent Code and the runtime loader.

Requirements:

  • Create a file named string_utils.c containing a function void reverse_string(char* str) that reverses a string in place.
  • Create a header file string_utils.h for the function prototype.
  • Compile string_utils.c into a shared object named libstringutils.so.
  • Write a main.c that takes a string from the user, calls reverse_string, and prints the result.
  • Compile main.c and link it against your shared library.
  • Run the program using the LD_LIBRARY_PATH environment variable to ensure the loader can find your .so file.

Verification: Your program should successfully reverse a string like "Software Engineering" into "gnireenignE erawtfoS" without needing the string_utils.c source code present during the final linking stage of main.c.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.