Static C Libraries
Almost like a real library…
What are static libraries in C?
At its core, a C static library is a file containing multiple object files (an object file contains a movable intermediary format of machine code, usually binary, that is produced by the compiler during compilation). When contained within a static library, these object files can be used as a single entity in the linking phase of compilation. This allows for an increase in the speed of the linking process for two reasons: first, any C library is usually indexed in a way that makes finding functions and variables easy, this means less time is spent in the linking phase with the compiler looking for code in an indexed library rather than searching through separate files on the disk; second, because libraries are condensed versions of multiple object files, there are less files the compiler has to search for. In summation, static libraries are a compilation of object files for use in linking; and libraries help to speed up compilation in the linking phase by allowing the compiler to search for fewer files in fewer locations.
How to create a static library
A static library is also technically an archive file, which is why the are read as ‘.a’ files. An archive file is a file composed of one or more computer files and their associated metadata (data that describes of data); sounds similar to our working idea of a static library, right? Knowing that static libraries are archived files leads us to the program used to create a static library, namely the ‘ar’ or ‘archiver’ program. This program is used to create libraries, as well as to modify the object files within, and list the contents of a given library with help from specific flags. The ‘c’ flag tells the archiver to ‘create’ the library if it is not already in existence, and the ‘r’ flag tells the archiver to ‘replace’ any older files with updated versions being added:
$ ar -rc libtest.a test_file.o test_file2.o test_file3.o.
In this example, the ‘ar’ program is called with the ‘r’ and ‘c’ to create a static library called ‘libtest.a’ which contains three object files: test_file.o, test_file2.0, and test_file3.0. To view the contents of a library, call the ‘ar’ program with the ‘t’ flag:
$ ar -t libtest.a
Obtaining object files
Lets take a moment to discuss how to obtain an object file from the compilation process, since they are the only useable type of file in a C static library. Compilation is done through a program, and here we will look at the GNU Compilation Collection (gcc). Turning a .c source file into a .o object file is an easy procedure, and is done using the ‘Wall’ and ‘c’ flags with the ‘gcc’ command:
$ gcc -Wall -c *.c
The ‘Wall’ flag is a warning option for the compiler and tells the compiler to consider a larger number of potential warning flags when compiling the source code (for a complete list of these warning flags look here). The ‘c’ flag tells the compiler to save the object file that it creates during the compilation process with the same name as the source file but denoted by the .o ending. In the line of code above, the unary wildcard ‘*’ is used to denote ‘all’ files ending in .c, so the compiler will create an individual object file for each source file it encounters.
Now that you have obtained your object code and archived it to a static library it’s ready to use, yes?.. no? Almost. Remember, part of why a library is useful is that it makes the linking process more efficient. This only happens if the library is indexed. To index a library, call the ‘ranlib’ command:
$ ranlib libtest.a
Indexing a library accomplishes two tasks: first it optimizes the time used for looking up symbols within the library; second, it assures that the order in which the symbols appear won’t matter during compilation. Why is the second part important? Because, much like any program, the order in which the object files are passed from the library to the linker is the order that the linker associates them to the final executable file.
What does this mean exactly? When dealing with static libraries, the linker is checking to see if any symbols used by the associated source files, but which were not defined in them, are instead defined in the library. If a symbol is found that matches this criteria, then the linker adds the entire object file from the library that contains the symbol to the executable file. Now, if multiple static libraries are being called on, the order in which they are called is important because the linker will not return to a previously processed library unless that library is called again. Here’s an example:
You have the source code from example.c, which has calls to a previously compiled library named ‘libfirst’. You compile it to obtain the example.o object code:
$ gcc -Wall -c example.c
Then you take the object code, example.o, and archive it to a library named ‘libexp’:
$ ar -rc libexp.a example.o
At this point you have a library ‘libexp’ which contains the example.o object file which contains calls to a separate library named ‘libfirst’. Here is where indexing and order play a key role. Because the ‘libexp’ library needs to access ‘libfirst’ in order to correctly compile, ‘libfirst’ must be called AFTER ‘libexp’. Remember, any library that the linker completely passes through will not be checked again unless called again explicitly. To think of it another way, the compiler does not know how to look for something if it doesn’t know what that something is.
Linking with a static C library
So far we have discussed what a static C library is, how to create one, and how to index a static library to maximize its usefulness. Now let’s discuss how to link a file that we wish to compile into an executable file with the necessary library. This is done with the use of certain flags when calling on the compiler:
$ gcc -o testprogram testprogram.c -L libtest.a
Here, we have the ‘o’ flag which tells the compiler to name the executable file ‘testprogram’. The source file is ‘testprogram.c’ and the library being linked is ‘libtest’. The ‘L’ flag tells the linker to find the ‘libtest’ library and to search for said library in the current working directory as well as the standard locations where the compiler usually looks for libraries.