C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
76: Static Libraries vs Dynamic Libraries
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.ccontaining a functionvoid reverse_string(char* str)that reverses a string in place. - Create a header file
string_utils.hfor the function prototype. - Compile
string_utils.cinto a shared object namedlibstringutils.so. - Write a
main.cthat takes a string from the user, callsreverse_string, and prints the result. - Compile
main.cand link it against your shared library. - Run the program using the
LD_LIBRARY_PATHenvironment variable to ensure the loader can find your.sofile.
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.
There are no comments for now.