Libraries & Linking
Introduction
Libraries are crucial to ensure reusability and modularity of code.
They contain one or more object files, which consist of object code.
C functions beneficial to multiple applications should be compartmentalized into libraries.
Libraries can reference functions in other libraries, e.g., standard C or math libraries.
Interface and Implementation
Interface: Defined in a header file (
.h
).Implementation: Defined in a
.c
file.
Linking
When a C program is compiled, the compiler generates object code (
.o
or.obj
).After generating object code, the compiler invokes the linker.
Linking combines multiple object files to create a single executable file.
Main goal: Make the library functions available to your program.
Can copy the library function code to object code.
Alternatively, it can ensure the code is available at runtime.
Static Linking: Library functions are copied to the executable file.
Dynamic Linking: Only the name of the library is in the binary file; the actual linking occurs when the program runs.
Static vs. Dynamic Linking
Static Linking:
Libraries are linked at compile-time.
Advantages: No runtime symbol resolution; once bundled, you're certain the correct library version is in use.
Disadvantages: Creates larger binaries; no way to update the library without rebuilding the whole program.
Dynamic Linking:
Libraries are linked at runtime.
Advantages: Saves on disk and memory; all programs linked to a library share a single copy of that library in memory.
Disadvantages: Small runtime penalty due to symbol resolution at runtime.
Library Types
Static Libraries (.a or .lib):
Uses static linking; each process has its own copy of code and data.
Dynamic Libraries (.so or .dll):
Linked at runtime; code is shared but data is specific to each process.
Creating Libraries
Static Libraries:
Created using ar
(archive) utility. Typical naming convention: lib<name>.a
.
Dynamic Libraries:
Created using the link editor (ld
). Typical naming convention: lib<name>.so
or .dll
.
Library Loading
Introduction
When a program uses a library, the library's contents must be made available to the program either at compile-time or runtime.
This process is called "loading". Depending on when and how the library's content is made available, loading can be categorized into different types.
Types of Library Loading
Static Loading (or Load-time Linking):
Occurs at compile time.
The code from the library is incorporated into the final executable during the linking phase.
The resulting binary becomes larger because it contains the code it needs.
If the library updates, the program must be re-linked and possibly recompiled to benefit from the changes.
Dynamic Loading (or Run-time Linking):
Occurs at runtime.
The library's code isn't included in the executable but is accessed as needed while the program runs.
Requires that the library is present on the system during execution.
The program can benefit from updates to a library without recompilation, just a restart.
Static vs Dynamic Loading
Advantages of Static Loading:
No dependencies required at runtime: Since all the code is bundled within the executable, there's no need for external libraries at runtime.
Execution speed: Directly contains all the necessary library code, which might make it slightly faster at startup.
Advantages of Dynamic Loading:
Smaller executables: Since library code isn't bundled, the executable size is smaller.
Shared libraries: Multiple programs can use a single library copy, conserving memory.
Updates: Programs can benefit from library updates without recompilation.
Dynamic Loading Mechanics
When a program requires a function from a dynamically loaded library:
The loader checks if the library is already loaded into memory.
If not, it finds the library on disk (using system paths and environment variables like
LD_LIBRARY_PATH
).The loader allocates memory and loads the library.
The program can then access and execute the library's functions.
Dynamic Loading API
The dynamic loading API allows explicit control over the loading and unloading of shared libraries at runtime.
dlopen(): Opens a dynamic library and returns a handle.
RTLD_LAZY
: Resolves symbols as needed.RTLD_NOW
: Resolves all symbols immediately.
dlsym(): Retrieves the address of a function or variable from the library using the handle.
dlclose(): Closes an opened library.
dlerror(): Fetches a human-readable error string for the most recent dynamic loading API error.
Dynamic Loading Practical Uses
Plugins: Software can be extended without modifying the core executable.
Modular Programs: Only load parts of the software that are needed, conserving resources.
Optional Features: If a library isn't present, the software can still run with reduced functionality.
Dynamic Loading Code Examples
Loading a Dynamic Library
Using a Function from a Dynamically Loaded Library
This example will dynamically load the math library (libm
) and utilize the cos
function:
libm
) and utilize the cos
function:Dynamically Loading Optional Features
Suppose we have a feature in our software that relies on a specific library. If the library is present, we use the feature; otherwise, the software runs with reduced functionality.
Remember, to compile any of these programs, you would typically use:
The -ldl
flag is necessary to link against the dynamic loading functions provided by dlfcn.h
.
Conclusion
While static loading offers simplicity and directness, dynamic loading brings versatility and efficiency, especially for large-scale or modular software.
The choice depends on the application requirements, distribution considerations, and the intended user experience.