In this tutorial, we will talk about libraries in C++ and how to create/use them. A library is a collection of pre-compiled code that can be re-used by programs. There are 2 types of libraries: static library and dynamic library.
1. Static Library vs Dynamic Library
A static library (or archive) contains code that is linked to users’ programs at compile time. The executable file generated keeps its own copy of the library code.
A dynamic library (or shared library) contains code designed to be shared by multiple programs. The content in the library is loaded to memory at runtime. Each executable does not maintain its replication of the library.
Here is the illustration of using a static library vs dynamic library. You can see the static library is included to be part of the executable. Whereas a dynamic library only needs to create a table of the symbols (functions, variables referenced in library code) in the program.
At runtime, the dynamic library is loaded to the memory only once in modern operating systems and shared across all programs depends on it. In contrast, when using static libraries, every executable must load the library code to the memory. The former can lead to more efficient memory utilization when there are more than one executables running. The comparison can be explained by the following graph.
Using static libraries lead to 2 obvious drawbacks:
1. Increasing the size of the application. The problem gets worse if the application contains multiple executables. You may end up keeping several copies of the same library.
2. Modifying/upgrading the library code requires rerun compiling/linking of other parts of the application. This can be a pain for deploying/maintaining purpose. Most of the time, a (non-interface related) dynamic library upgrade does not require recompiling other parts.
Normally, people choose dynamic libraries over static due to the above reasons. However, dynamic libraries are not perfect. They have their own hurdle for developers — requiring extra concern on installing. Unlike a static library that generates a monolithic package, a dynamic library must be located appropriately to make sure the executable can find libraries at runtime.
2. Create and Use a Static Library
In this example, we will create a toy library with one reciprocal function. The library sources contain a header file my_math.h and source file my_math.cpp:
The header file my_math.h is included in main.cpp which calls the function from the library:
In the first compile, we treat my_math.cpp just like a common source file and everything works as expected:
Now let’s wrap my_math as a static library. The process involves 2 steps. Step 1 is to generate the object file my_math.o using the same command above. Step 2 involves using ar (a Linux archive utility tool) to create the library file:
The “cr” flag is to indicate creating a new static library file. It is followed by the output file name first as a request. Notice the name of output is “libmy_math.a”. It is a convention to name the file libXXX.a in Linux, please always do it. When the library is used, the command line tool actually relies on this convention for the linker to work properly.
Now we want to use the static library file. One way is to put the file together with other object files in the g++/gcc linking command.
Another method more often used is to explicitly specify library path using (-L) and library name (-l):
This tells the compiler to look for libraries in path (.) with name libmy_math.a. Notice here we use -lmy_math. The linker will treat this as specifying file name libmy_math.a (Remember the naming convention for creating library we just talked about).
We can verify that the library has been copied to the executable by deleting the library and running:
It works. We just created a static library and used it in our program!
3. Create and Use a Dynamic Library
Let’s use the same sample code and instead create a dynamic library this time. Here is the command:
The “-shared” flag instructs to generate a shared library. Again, the output file naming convention libXXX.so is a must and will be utilized by the linker later.
Similar to static libraries, we have 2 ways to use it. 1. Put it as an input to the linker/compiler. 2. Explicitly specify library location (-L) and name (-l):
Simple, right? Let’s run it:
Oops, we got an error (with either executable generated). The runtime is trying to find a shared library named libmy_math.so but can’t. What happened and how to fix it?
Turns out the user must provide hints to the executable or OS for the runtime to find the shared library (libmy_math.so). There are 2 ways in Linux:
- Append the shared library path to the environment variable LD_LIBRARY_PATH.
- Use -rpath flag to specify the shared library path when building the executable.
Let’s test them:
- Add library path to LD_LIBRARY_PATH:
At runtime, OS searches through every path in LD_LIBRARY_PATH (separated by “:”) to find the dynamic library it needs. By appending the path to LD_LIBRARY_PATH, we fixed it.
2. Use -rpath flag:
Here “-Wl flag” means what follows it (which is a comma-separated list of flags) will be are passed to the linker. In this case, “-rpath /home/cpp_tutorial/static_library” is passed to the linker. The linker inserts this path information to the executable’s (a.out) own search path. This also works.
Compare the 2 methods, modifying LD_LIBRARY_PATH involves changing global variables which affects all programs. Using -rpath is usually a preferred way because it is a local change and does not alter behaviors of other executables. To know more about how shared library search path works, you can read Shared Libraries: Understanding Dynamic Loading.
4. Summary
In this tutorial. We learned the basics of C++ libraries:
- There are 2 types of libraries: static library and dynamic(shared) library.
- Static libraries are copied into the executable at compile time.
- Dynamic libraries are not copied, but loaded and linked at runtime.
- How to create static/dynamic libraries in Linux and use them.
- For dynamic libraries, there are 2 ways to specify a library search path: using LD_LIBRARY_PATH or -rpath.
Reference
Shared Libraries: Understanding Dynamic Loading