Before we begin, let’s put ourselves in context … When developing computer programs, we quickly realize the benefits of code reuse!
We immediately visualized that it would be ideal to have a separate directory for the programs that contains all the functions and classes already compiled, to be used by different applications. Some of the enormous advantages would be for example:
1. Code reuse
2. Reduced compilation time
3. Reliable compiled code
All of this really becomes very relevant and obvious when executables are no longer trivial or small. As programs grow and are modified, enhancements, tweaks, and fixes are made, causing them to be compiled countless times.
This leads both independent programmers and corporate software departments to purchase their own sets of tools, which become part of the arsenal of development resources, generating huge benefits with the consequent financial impact on projects.
And the ideal is possible… The way to do it is to make libraries, in which we have one or more functions already compiled and ready to be used in any program in which we need them.
Until now we could say that we answered what they are for and why to use them, but …
What is a library, and how do they work?
Libraries are a tool provided by compilers, it is a file that contains several object files, which can be used as a single entity in a linking phase within the compilation process of a program. Normally, they are indexed which allows you to easily find symbols in them (functions, variables, etc.); that is why linking a program whose files are organized in libraries is faster.
UNIX systems allow us to create two types of libraries: Static Libraries, and Shared or Dynamic Libraries.
Static libraries are just collections of object files that are linked into the program during the linking phase of compilation and are not relevant during runtime. This last comment seems obvious, as we already know that object files are also used only during the linking phase, and are not required during runtime — only the program’s executable file is needed to run the program.
Static libraries, also called object-libraries (in relation to the fact that their components or modules include files of this type), are collections of object-files grouped in a single file, generally with a
.a extension, accompanied by files header, generally
.h, containing the declarations of the objects defined in the library. Later, during the linking phase, the linker includes in the executable the modules corresponding to the functions and library classes that have been used in the application. As a result, such modules become part of the executable, in exactly the same way as any other function or class that would have been written in the body of the application.
Dynamic libraries (also called shared libraries) are linked to the program in two stages. First, during compile time, the linker verifies that all the symbols (again, functions, variables, and the like) required by the program, are either linked into the program or in one of its shared libraries. However, the object files from the dynamic library are not inserted into the executable file. Instead, when the program is started, a program in the system (called a dynamic loader) checks out which shared libraries were linked with the program, loads them to memory, and attaches them to the copy of the program in memory.
The complex dynamic loading phase makes the launch of the program a bit slower, but this is a very minor drawback, which is outweighed by a huge advantage: if a second linked program is running with the same shared library, you can use the same copy of the shared library, which saves a lot of memory. For example, the “C” library is typically a shared library and is used by all C programs. However, only one copy of the library is stored in memory at any one time. This means that we can use much less memory to run our programs, and the executable files are much smaller, which also saves a lot of disk space.
How to create a library?
The first thing to do is to put all the relevant files in the same directory. We must include the header file that contains the prototypes of the relevant entities.
And something that will save a lot in the process, is to make sure that the header file contains the macros (
#ifndef <HEADERFILE>_H and
#define <HEADERFILE>_H at the top and
#endif at the bottom so that the file of the header, defending itself once instead of each time it is called) so that the header file is defined only once instead of each time it is called.
After the preparation, we begin with the steps for creating the library.
Step 1: To create a static library
Using GCC, we compile all the source
.c files, with the
-c option so that the compiler does not link the object files yet, but creates a counterpart
.o object, for each source
.o file; run the following command:
$ gcc -Wall -pedantic -Werror -Wextra -c *.c
You can see the meaning of the options in the following link: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#Warning-Options
Here in the above command, all the
.c extension files in the current working directory have been converted into their respective object files
.o. Once we have object files, we use the GNU ar command to create our final library/archive (is a Unix utility that maintains groups of files as a single archive file).
Step 2: “Bundle up” the functions into a single file.
The basic tool used to create static libraries is a program called
ar “archiver”. This program can be used to create static libraries (which are actually archive files), modify object files in the static library, list the names of object files in the library, etc.
Once all the object files (.o) have been created, it is time to create the static library file (.a), and we can do it using the following command:
$ ar -rc libholberton.a *.o
This command creates a static library called
libholberton.a. In this command, the
r flag ensures that older files will be updated by being replaced with new object files. The
c flag means that the library will be created if it does not already exist. Finally, the
*.o is a wildcard operation to include all files ending with
.o in the static library.
It is important to note the format for naming a
Step 3: Library indexation
After creating or modifying a file, it needs to be indexed, which is done with the
ranlib command. This command creates a header in the library with the symbols from the object file. The compiler uses this index later to speed up the search for symbols within the library and to ensure that the order of symbols in the library does not matter during compilation. Consider that a large library can have thousands of symbols, which means that an index can significantly speed up the search for references.
The command used to create or update the index is invoked as follows:
$ ranlib libholberton.a
Note: When an archive’s index generation date (stored within the archive) is earlier than the file’s last modified date (stored on the file system), a compiler attempting to use this library will it will complain that its index is out of date and abort. There are two ways to overcome the problem:
ranlibto rebuild the index.
When copying the archive to another location, use
cp-p, instead of just
cpthat all file attributes are preserved, including its access permissions, the owner (if a superuser invokes
cp), and its last modified date. This will make the compiler think that the index within the file is still up to date. This method is useful for makefiles that need to copy the library to another directory for some reason.
Step 4: Checking the contents of the library
To see the content of our library, we can use the following command with the
$ ar -t libholberton.a
And if we want to see the symbols in our library, we can do so using the
nm command, which lists the symbol value of each symbol, the symbol type, and the symbol name of the object files. We invoke it in the following way:
$ nm libholberton.a
Note: that if you combine all functions in one file and turn that into one object file in the archive, the
nmcommand will still list all functions, while the
ar -tcommand will list only one object file.
Using Static Library
To use a static library, we must consider that it is only linked during binding, so we must tell gcc where to look for it.
By default, the program looks for them in the standard library directories (such as,
/usr/local/lib) if no other path is specified. If the library is in one of these directories, the following syntax can be used to link the library to the program:
gcc program_code.c -l<name> -o program_code
Continuing with the example, with the static library that we have created
libholberton.a, we are going to use it by invoking it as part of the compilation and linking process when creating an executable program, with the following command:
gcc main.c -L. -lholberton -o main
-loption eliminates the need to type out the lib prefix and .a extension for the library. In other words,
-Lspecifies the path to the library. We can use
-L.to point to the current directory and
-L/home/tmpto point to the
This will create an executable main program using the object file
main.o, and whatever symbols it requires from the static library
One thing to note is that the order of the library placement in the gcc argument matters. The source program should always come BEFORE the library. Any program listed after the library in the gcc argument will not be linked. For more details on this, I suggest Eli Bendersky’s piece on Library Order in Static Linking.
For the example, we assign the main program to print on the standard output of the alphabet in lowercase and uppercase; and to visualize the result we execute the following command:
Executables generated using static libraries are no different from executables generated from individual source or object files. Static libraries are not required at run time, so there is no need to include them when you distribute your executable. At compile time, linking to a static library is generally faster than linking to individual source files.
Static linking is very simple, but it has some downsides or downsides that you can see:
- If the code in the library is updated (for example, to correct a bug), you must recompile your program into a new executable.
- Each system program that uses that library contains a copy in its executable; which makes it very inefficient and a hassle when a bug is found because it has to be recompiled, per point one.
It is in these cases that we find dynamic or shared libraries better. Shared libraries are an elegant way to troubleshoot a static library. A shared library is a library that is dynamically loaded at run time for each application that requires it.
I want this post to be of help to those who read it, carrying it out is part of my training at Holberton Montevideo.
It will be until next time, happy learning!