Much Ado About Libraries
Static libraries and dynamic libraries have a lot in common. Like static libraries, dynamic libraries are generated based on pre-compiled object files. The main difference between the two is that unlike static libraries, where all the code in these object files is copied over into the library, dynamic libraries only include the names in the binary file, with all actual linking done when the program is run in a process called dynamic linking.
Both dynamic linking and static linking have advantages and disadvantages, namely how statically linked libraries sacrifice file size for performance, and how dynamic libraries have a vastly reduced size and compilation time, at the cost of longer execution times and the risk of compatibility issues.
Now that we know why dynamic libraries might be used, how do we make them? The first step is to generate your object files from c source code using the gcc command
gcc [compilation flags] -c -fPIC [filenames] for dynamic libraries, or simply
gcc -c [filenames] for a static library.
The -c flag tells the gcc compiler that we want object files instead of an executable, so it should stop at that stage of compilation.
This flag stands for Position Independent Code, meaning that the code generated does not require a specific location to work. This allows the library to be loaded into spaces where it won’t overlap with other memory.
At this point we have object files, so the next step is to turn it into a library. Dynamic libraries are created using the gcc command
gcc -shared *.o -o lib[library name].so , but if you want a static library it’s a little different. Static libraries use a command called
ar , or archive, to create an archive for the object files to be stored. The specific command is
ar rcs [libraryname].a [objectfile(s)].o . The
rcs flags tell the compiler to replace any pre-existing files in the archive with the same name, allowing the library to be updated, to create the library if it does not already exist, and o write an index of the object files into the archive, even if there aren’t any actual changes respectively.
This tells the compiler that the object files being gathered by *.o are to be converted into shared object files, aka a dynamic library.
At this point we have finally generated both our dynamic and static libraries, so the next step is to use it. Before we can use it, however, we have to do one last thing for both. With static libraries, it is important to make sure the library was archived and indexed properly. Some archives do this automatically but if it hasn’t it can be done with the
ranlib command. To check the symbols in the library, you can also simply use the
nm command. Dynamic libraries are a bit more involved, as we have to tell our machine where to find it. To check if your terminal already has all the library information, you can just use the command
ldd [filename], which lists all the libraries required by the executable and if/where they were found. By default, libraries are stored in the /bin/lib folder, so unless you’re working there you’ll need to add the directory it is located to your library path by using the command
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH. If you expect to be using it from another directory you might add an absolute path, but since we will be calling it from the current directory simply using a ‘.’ is fine to say start from the current folder.
Now that the computer knows where to find our library, it is finally time to compile a main file using our library. To do this, we say
gcc [compilation flags] -L main.c -l[libname] .
Tells gcc to look in the specified directory, in this case the current directory.
The -l flag tells gcc to look for a library with the name [libname]. So if your library was named ‘tools’, -ltools would tell gcc to look for a library file called ‘libtools.so’.
And just like that, we now have a working executable using our shared library.