Static and dynamic libraries in C

Sebastián Olmos
9 min readDec 15, 2020

If you are a developer, you will reuse code created by you or provided by others. In programming, a library is a collection of precompiled pieces of code that programs can reuse. Libraries simplify life for programmers by providing a collection of resources such as subroutines / functions, classes, type values ​​or specifications, data structures, etc., that make it more dynamic and easier to reuse code in the programs.

UNIX systems allow us to work with two types of libraries: Static library and Dnamic library.

A static library is a single file, named by convention in the form lib lib<your_lib_name>.a. Obtained as part of the compilation process of the object .o files (non-executable binaries) after the indexing process. Indexing creates a header in the library with the symbols from the object file. The compiler uses this index later to speed up searching for symbols within the library and to ensure that the order of symbols in the library does not matter during compilation. It is relevant to consider that a large library can have thousands of symbols, which means that an index can significantly speed up the search for references.

A static library contains code that is linked to the program at compile time. The generated executable file saves its own copy of the library code. Although these libraries can be used by various programs, once the copy is part of a program, they can only be accessed by the program with which it was compiled.
Therefore, any modification or update that is required of them, require a new compilation process to create a new executable.

A dynamic library (or shared library) contains code designed to be shared by multiple programs. The contents of the library are loaded into memory at run time. Each executable does not maintain its replica of the library. Dynamic or shared libraries, on the other hand, exist as separate files outside of the executable file. That is why updating or modifying them (the libraries) do not require a new compilation process for each of the programs that use them.

Shared libraries are lib<your_lib_name>.so files on Linux (in Windows .dll, or in OS X .dylib). These are dynamically linked by simply including the address of the library. Dynamic linking links the libraries at run time. Thus, all functions are in a special place in the memory space, and each program can access them, without having multiple copies of them.

Here is an illustration comparing the use of a static library versus a dynamic library. You can see that 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 the library code) in the program.

At run time, the dynamic library is loaded into memory only once and shared among all programs. In contrast, when using static libraries, each executable must load the library’s code into memory. The former can lead to more efficient memory utilization when more than one executable is running.

From what has been seen so far, we can consider that the use of static libraries has some drawbacks, such as:

  • Increase the size of the application. The problem worsens if the application contains multiple executables. You can end up keeping multiple copies of the same library.
  • Modifying / updating the library code requires rerunning compilation / linking from other parts of the application. This can be tricky to implement / maintain purpose. Most of the time, a dynamic library update (not related to the interface) does not require recompiling other parts.

Typically, people choose dynamic libraries over static libraries due to the above reasons. However, dynamic libraries are not perfect, they require additional care in installation. Unlike a static library that builds a monolithic package, a dynamic library must be properly located to ensure that the executable can find libraries at run time.

Generally, the location of shared libraries is stored in the following paths:
/lib, /lib64, /usr/local/lib.
Therefore, when we write or use shared libraries, as system administrators, we must manage and install them; for this we can use the Linux library management commands, such as:

  1. ldconfig : Updates the necessary links for the run time link bindings.

The ldconfig command allows you to create, update, and remove the necessary caching and links (for use by the runtime linker, ld.so) to the latest shared libraries found in the directories specified on the command line, in the /etc/ld.so.config file, and in trusted directories (/usr/lib, /lib64 and /lib). The ldconfig command checks the header and file names of the libraries it finds when determining which versions to update their bindings. This command also creates a file called /etc/ld.so.cache that is used to speed up linking.

For example, if we have installed a new set of shared libraries in /usr/local/lib, we can visualize them with the following command:

$ ls -l /usr/local/lib/

And to check new libraries or search for a linked library, run the command:

$ ldconfig -v
  1. ldd : Tells what libraries a given program needs to run.

ldd (List Dynamic Dependencies) is a Unix and Linux program to display the shared libraries required by each program.

The basic usage of ldd is pretty simple — just run the ‘ldd’ command along with an executable or shared object name as input.

$ ldd [object-name]

This command also allows you to report missing functions, missing objects, determine the compatibility or not of a feature, among other options.

How to create and use them (Linux only)

Static libraries

To make a static library, you need to create a file that will contain the functions that you will call in your c program. We gather all the source files .c, with the header file .h .

To make it more illustrative, we will use an example by creating a file called my_libray.c that contains a function called print_example.

/* Filename: my_library.c */
#include <stdio.h>
void print_example(void)
{
printf("When function is called this line is printed!");
}

And the header file for our c program to include.

/* Filename: my_library.h */
#ifndef MY_LIBRARY_H
#define MY_LIBRARY_H
void print_example(void);
#endif

Now we need to convert the .c files into object type files (which will have the same name, with the extension .o); to do this we execute the following command:

$ gcc -c my_library.c -o my_library.o

We use the bander -c to stop compilation before the binding process; and the -o flag to write the compilation output to an output file with a .o extension.
The last process to create the static library consists of two steps.

The first step is to generate the object file using the same command above. The second step involves the use of the ar program (archiver, a Linux archive utility tool) that will allow us to produce a static library file, with the suffix .a, by executing the following command:

$ ar rcs lib<my_library>.a my_library.o

The rcs flag is to indicate the creation of a new static library file. It is followed by the name of the output file first as a request. Notice that the name of the output is lib<my_libray>.a. It is a convention to name the file lib<name_library>.a on Linux, please always do so. When using the library, the command line tool relies on this convention for the linker to work correctly.

After creating (or modifying a file), we need to index it, 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 searching for symbols within the library and to ensure that the order of symbols in the library does not matter during compilation. It is important to 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 lib<my_library>.a

To see the content of our library, we can use the following command with the -toption:

$ ar -t lib <my_library> .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 from the object files. We invoke it in the following way:

$ nm lib <my_library> .a

With this process we have created a static library from the previously created object files. Now to make use of the library, we have to create a program that uses them. Continuing with the example, we will create a program that uses our print_example function.

/* filename: test_function.c  */
#include "my_library.h"
void main()
{
void print_example(void);
}

Now we need to compile our test program at the object file stage, so we create test_funciotn.o, an object file with the object code of our program. For the compilation, we execute the following command:

$ gcc -c test_function.c -o test_function.o

Finally, we need to link our program with the static library.

$ gcc -o test_function test_function.o -L. -lmy_library

The -L. flag tells the compiler to look in the current directory for the static library and the -l option gives the name of the library.

And finally to see the result, run the program as any executable as follows:

$ ./test_function
When function is called this line is printed!

Dynamic libraries

To create dynamic libraries we need to first create the file that will contain our function definitions as we did in creating a static library.

/* Filename: my_library.c */
#include <stdio.h>
void print_example(void)
{
printf("When function is called this line is printed!");
}

We will also create a test_file to test our dynamically linked library.

/* filename: test_function.c  */
#include "my_library.h"
void main()
{
void print_example(void);
}

And, we will also need to create a header file for your c program to include.

/* Filename: my_library.h */
void print_example(void):

Now we need to compile the source .c file, ready to use into a dynamic library. Because multiple programs can all use one instance of a dynamic library, the library cannot store data at fixed addresses. This is because the location of the library in memory will vary between programs. This is done using the compiler flag -fpic. Since we need to apply this step after the compilation process has generated the object code, the compiler must be instructed to stop and return an object file .o for each source file; and this is done using the -c flag.
It should be remembered that if we have several .c files we can use the *.c wildcard to include all files with this extension, avoiding detailing each one of them (gcc -fpic *.c -c).

gcc -fpic my_library.c -c

The object files are now ready to compile into a dynamic library. This is done by compiling all the .o files using the -shared -shared flag. Now we will compile the object file my_libray.o, into a dynamic library file (with extension .so), which we will calllibray.so; to do this we execute the following command:

gcc -shared -o library.so my_library.o

Later, when compiling program files, the compiler identifies a library by looking for files that begin with "lib" and end with a library extension (.so for dynamic, .a for static). Therefore, it is important to name a library accordingly.

Finally, we’ll need to export the path for libraries so that programs know where to look for them by executing the following command: export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH

Now when we compile our test file to produce the executable.

gcc test_function.c -o test_function

To run our program you just need to run the executable test_function.

./test_function
When function is called this line is printed!

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!

--

--