Static vs Dynamic libraries.
The more code we write, the more we realize that some parts or chunks of code are repeatedly used in many programs. Something as repetitive like a mathematic operation can be re-used over and over in virtually every program we write.
A library allows us to have those repetitive functions outside the specific programs and be able to utilize them whenever we need. There are huge advantages associated to that, being the most evident not having to re-write the code over an over again every time we need to use the same function.
Libraries are already-compiled and usage-ready functions to be used by any program we write.
In order to insert our code in a library, it must be organized in the following way:
- One or more .c source files containing the code for our functions.
- One or more .h header files containing the types (typedefs, structs) and prototypes of the functions we want to have available for utilization.
library1.cint sum (int a, int b)
{
return a+b;
}
int sub (int a, int b)
{
return a-b;
}library1.h#ifndef _LIBRARY_1_H
#define _LIBRARY_1_H
int sum (int a, int b);
int sub (int a, int b);
#endif
Static and dynamic.
In Linux we can create two types of libraries: statics and dynamics.
An static library is “copied” into our program when we compile it. Once we have the executable file for our program, the library is no longer used by it. In fact, we could even delete it and the program would continue to work properly as it has copied everything it requires from the library. By the way, the program would only copy what it requires from the library. If the library contains two functions but only one is called by the program, that would be the only one copied over.
A dynamic library is NOT copied into the program. Whenever we are running our executable, each time the code requires something from the library it will go and get it from there. If the library is deleted, the program will return an error.
What are the advantages and disadvantages of each type of library?
- A program compiled with static libraries is larger, as a copy of everything required from the library is created. For static libraries memory footprints are also larger.
- A program compiled with static libraries can even be moved over to a different computer without the need to move the libraries along with it. In static libraries, once everything is bundled into your application, you don’t have to worry that the client will have the right library (and version) available on their system.
- A program compiled with static libraries is (at least on paper) faster on its execution. When it calls a function from the library, it is actually on its own code. Therefore, there is no need to access the library file to retrieve information from it.
- One drawback of static libraries is, for any change in the static libraries, you have to recompile the main program every time.
- If we change or even remove a static library, already created executables are not affected. However, if we change a dynamic library, executable will be affected. This could be an advantage, if we have amended the library in order to correct an error (as the error will be fixed in all executables).
- Dynamic libraries will conserve memory since each program can access the library without creating an individual copy of the functions.
Ultimately, deciding what type of library will be used is a negotiation between the advantages and inconveniences of each one, affected by the purpose of the software we are designing.
For smaller, simpler programs, static libraries are often used. Dynamic libraries are commonly used for larger programs or system libraries.
In Unix, static libraries are normally named libname.a and dynamic libraries are named libname.so.
Creating static libraries using GNU.
Let’s assume we have a (very) simple C program called lib_mylib.c:
#include <stdio.h>
void fun(void)
{
printf("fun() called from a static library");
}
We also have a header .h file with the function prototype on it:
#ifndef LIB_MYLIB_H
#def LIB_MYLIB_Hvoid fun(void);#endif
In order to create a library (regardless of it being static or dynamic), we must first obtain the .o files from our .c files. The .o or object code files contain the compiled content of the corresponding .c file.
So, in order to create the .o file from the lib_mylib.c program, we must do the following entry:
gcc -c lib_mylib.c -o lib_mylib.o
In order to create the static library we must use the command ar (ar as in archive). This step is to bundle multiple object files in one static library:
ar rcs lib_mylib.a lib_mylib.o
From this point on the library can be called from another program. In order to test this, let’s create a program called driver that uses the newly created static library:
#include "lib_mylib.h"void main(){
fun();
}
Let’s then compile it:
gcc -c driver.c -o driver.o
We must link the compiled driver program to the static library. Note that -L. is used to tell that the static library is in current folder.
gcc -o driver driver.o -L. -l_mylib
In the above command it is worth noting that the source code, driver in this case, needs to be listed before the -l
flag. The expression, -l
combined with _mylib
tells the compiler to look for an archive called lib_mylib.a. This is why is so important to use the standard format for naming the libraries.
Finally, we can execute the driver program:
./driver
fun() called from a static library
Creating dynamic libraries using GNU.
The process starts in a similar way than before, by creating the .o files out of the source .c files. That is done quick and easy with the following command:
gcc *.c -c -fPIC
The -fPIC flag is an instruction for the code to be position-independent. This means that the generated machine code is not dependent on being located at a specific address in order to work. The -c flag ensures that each .o file is not linked yet.
Next step is to enter this command:
gcc *.o -shared -o libname.so
The -shared flag specifies that you intend to create a dynamic library.
Finally we must export the path for libraries for programs to know where to look for the specific library. This is done by using this command:
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
Let’s go a little deeper on this last step.
In Linux, the environment variable LD_LIBRARY_PATH is set of directories where libraries should be searched for first. It tells the dynamic link loader where to search for the dynamic shared libraries an application was linked against. The directories specified in LD_LIBRARY_PATH get searched before the standard locations.
A few useful tools when working with libraries:
The nm command will list symbols from object files, or when used when -D (“D” as in “dynamic”), it will basically show you the content of the library:
$ nm -D libholberton.so
0000000000000b1c T _abs
0000000000000b4f T _atoi
0000000000202048 B __bss_start
w __cxa_finalize
0000000000202048 D _edata
0000000000202050 B _end
0000000000000bf4 T _fini
w __gmon_start__
0000000000000910 T _init
0000000000000b13 T _isalpha
0000000000000b2e T _isdigit
0000000000000b0a T _islower
0000000000000b25 T _isupper
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
0000000000000a75 T main
0000000000000ba9 T _memcpy
0000000000000b97 T _memset
U printf
0000000000000ae0 T _putchar
0000000000000b37 T _puts
0000000000000b59 T _strcat
0000000000000bba T _strchr
0000000000000b89 T _strcmp
0000000000000b41 T _strcpy
0000000000000a9f T _strlen
0000000000000b67 T _strncat
0000000000000b78 T _strncpy
0000000000000bd7 T _strpbrk
0000000000000bc9 T _strspn
0000000000000be5 T _strstr
U write
The ldd command will list the shared libraries required by each program or shared library specified on the command line. You can do this simply by typing ldd and the name of your executable file:
$ ldd len
linux-vdso.so.1 => (0x00007ffd391b8000)
libholberton.so => /home/vagrant/holbertonschool-low_level_programming/0x18-dynamic_libraries/libholberton.so (0x00007f91da758000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f91da388000)
/lib64/ld-linux-x86-64.so.2 (0x00007f91da960000)
Finally ldconfig is used to create the necessary links and cache to the most recent shared libraries found in the directories specified on the command line, in the file /etc/ld.so.conf, and in the trusted directories (/lib and /usr/lib).
ldconfig checks the header and filenames of the libraries it encounters when determining which versions should have their links updated.