Static and Dynamics “C” Libraries

The two types of “C” libraries

A static library is a file that consists of a collection of object files (*.o) that are linked into the program during the combing phase of compilation and are not pertinent during runtime.

While dynamic or shared library is a collection of functions compiled and stored in an executable with purpose of being linked by other programs at run-time.

An object file is a computer file containing object code, that is, machine code output of an assembler or compiler.

Why do we use libraries?

In the library we can include files from our programs that contains functions we may want to use in another program.

Without the library, we would have to declare and define each function we are going to use. But, since the functions are already incorporated in the files of our library, we will only need to add the library to our program.

Also It is faster to link a function from a C library than to link object files from a separate memory sticks or discs.

How to create these libraries?

As I said before, we have to compile using the ‘c’ flag in all the files that we would like to include in our library.

$ gcc -c -Wall -Werror -Wextra *.c

Once we have object file(s), we can now insert all object files into one static library.

A program called ‘ar’, (for ‘archiver’) is what we use to create a static library, which are actually archive files. It is also used to modify object files, list the names of object files, etc.

To create the library we use the following command:

$ ar -rc libname.a *.o

‘libname.a’ is the name of static library created by this command and this puts copies of all the files with the .o extension (object files).

The ‘c’ flag instructs ‘ar’ to set up the library if it doesn’t already exist. And the ‘r’ flag tells it to reinstate older object files in the library, with the new object files.

After an archive is created, it should be indexed, which can be done with the subsequent command:

$ ranlib libname.a

As the same as the static library we also have to compile using the “c” flag in all the files that we would like to include in our library. But in this case we have to add the “fPIC” flag that ensures that the code is position-independent. This means it wouldn’t matter where the computer loads the code into memory.

$ gcc *.c -c -fPIC

Then, to create the library, we have to type:

$ gcc *.o -shared -o liball.so

Here we are telling the compiler to compile all the object files (*.c)

into a dynamic library which is specified by the “shared” flag.

And “liball.so” is the name of our library. You can substitute the ‘all’ for the name that you want for your library.

The naming convention for dynamic libraries is such that each shared library name must start with lib and end with .so .

Now we have both libraries ready to use.

But, how do we use them?

Simply, by adding the library’s name to the list of object file names given to the linker, using a special flag, normally '-l'.

gcc main.c -L. -lstatlib -o prog

This will create a program using object file “main.o”, and any symbols it requires from the “statlib” static library.

Note the usage of the '-L' flag - this flag tells the linker that libraries might be found in the given directory ('.', refering to the current directory), in addition to the standard locations where the compiler looks for system libraries.

Dynamic Library.

In order to open and load the shared library, one should use the dlopen() function. It is used this way:

#include <dlfcn.h> / * define dlopen (), etc. * /
.
.
void * lib_handle; / * open library handle * /lib_handle = dlopen ("/ full / path / to / library", RTLD_LAZY);
if (! lib_handle) {
fprintf (stderr, "Error during dlopen ():% s \ n", dlerror ());
output (1);
}

The dlopen() function gets two parameters. One is the full path to the shared library. The other is a flag defining whether all symbols refered to by the library need to be checked immediatly, or only when used. In our case, we may use the lazy approach (RTLD_LAZY) of checking only when used. The function returns a pointer to the loaded library, that may later be used to reference symbols in the library. It will return NULL in case an error occured. In that case, we may use the dlerror() function to print out a human-readable error message, as we did here.

After we have a handle to a loaded shared library, we can find symbols in it, of both functions and variables. We need to define their types properly, and we need to make sure we made no mistakes. The compiler won’t be able to check those declarations, so we should be extra carefull when typing them.

The final step is to close down the library, to free the memory it occupies. This should only be done if we are not intending to use it soon. If we do — it is better to leave it open, since library loading takes time. To close down the library, we use something like this:

dlclose(lib_handle);

This will free down all resources taken by the library (in particular, the memory its executable code takes up).

Finally, the dynamic loading library gives us the option of defining two special functions in each library, namely _init and _fini. The _init function, if found, is invoked automatically when the library is opened, and before dlopen() returns. It may be used to invoke some startup code needed to initialize data structures used by the library, read configuration files, and so on.

Differences between static and dynamic libraries

Shared libraries are .so files. All the code relating to the library is in this file, and it is referenced by programs using it at run-time. A program using a shared library only makes reference to the code that it uses in the shared library.

Static libraries are .a files. All the code relating to the library is in this file, and it is directly linked into the program at compile time. A program using a static library takes copies of the code that it uses from the static library and makes it part of the program.

Advantages and drawbacks of each of them

  • Reduce the amount of code that is duplicated in each program that makes use of the library, keeping the binaries small
  • Allows you to replace the shared object with one that is functionally equivalent
  • Have a small additional cost for the execution of the functions as well as a run-time loading cost as all the symbols in the library need to be connected to the things they use
  • Can be loaded into an application at run-time
  • Increase the overall size of the binary, but it means that you don’t need to carry along a copy of the library that is being used
  • As the code is connected at compile time there are not any additional run-time loading costs