Statics Libraries VS Dynamics Libraries

Statics Libraries VS Dynamics Libraries

As we make computer programs, we realize that some parts of the code are used in many of them. For example, we can have several programs that use complex numbers and the addition, subtraction, etc functions are common. It is also possible, for example, that we like to make games, and we realize that we are repeating over and over again the code to move an image (a Martian or Lara Croft) across the screen.

It would be excellent to be able to put those functions in a separate directories of the specific programs and have them already compiled, so that we can use them whenever we want. The huge advantages of this are:

  • Do not have to rewrite the code (or copy-paste).
  • We will save the time to compile each time that code is already compiled. In addition, we already know that while doing a program, testing and correcting, you have to compile between many and "many more" times.
  • The already compiled code will be tested and reliable. Not the first times, but when we have already used it in 200 different programs and have been correcting the errors.

The way to do this is to make libraries. You have to keep an eye out when we do them so as not to get any dependence on something concrete in our program. For example, if we do our function of moving the image of Lara Croft, we will have to do the function in such a way that it supports any image, since we would not be hit anything by Lara Croft jumping in a "space invaders" style game.


No hay texto alternativo para esta imagen


How we have to organize our code

In order to put our code in a bookstore, we need to organize it as follows:

  • One or more source files . c with the code of our functions.
  • One or more header files . h with the types (typedefs, structs and enums) and prototypes of the functions we want that can be used.

As always, let’s make an example. The files would be these:

library1.h
#ifndef _LIBRARY_1_H
#define _LIBRARY_1_H
int suma (int a, int b);
int resta (int a, int b);

#endif


library1.c
int suma (int a, int b)
{
    return a+b;
}

int resta (int a, int b)

{
    return a-b;

}

It is a file with a pair of simple addition() and subtraction() functions.

An important detail to keep in mind are the #define of the header file (.h). When we make a bookstore, we don’t know in which future programs we will use it or how they will be organized. Suppose in a future program that there is a file of header list1.h that makes #include of ours. Imagine that there is also a list2.h that also makes #include of ours. Finally, with a little more effort, let’s imagine that there is a third list3.c that does #include of list1.h and list2.h, that is, more or less the following:

list1.h
#include <library1.h>

...



list2.h
#include <library1.h>
.
...


list3.c
#include <list1.h>
#include <list2.h>

...

When we compile list3.c, depending on what you have defined in library1.h, we will get an error. The problem is that by including file1.h, everything in that file is defined, including the library1.h. When you include list2.h, you try again to define the contents in library1.h, and you get an error that those definitions are defined twice.

The way to avoid this problem, is to put all the definitions inside a block #ifndef - #endif, with the name (_LIBRARY_1_H in the example) that we like best and different for each of our header files. It is usual to put this name preceded by _, finished in _H and matching the name of the header file, but in capital letters.

Within the block #ifndef - #endif, we make a #define of that name (no need to give it any value, just need to be defined) and then we define all our types and prototypes of functions.

When we include this file for the first time, _LIBRARY_1_H will not be defined, so it will be entered into the block #ifndef - #endif and all types and prototypes of functions will be defined, including the same _LIBRARY_1_H. When we include it for the second time, _LIBRARY_1_H will already be defined (from the previous inclusion), so it will not enter the block #ifndef - #endif, and nothing will be redefined for the second time.

It is a good habit to do this with all of our . h, whether they are for bookstores or not. If you notice any . h of the system you will see that you have this kind of thing until you get bored. For example, in /usr/include/stdio. h, the first thing after the comments, is a #ifndef _STDIO_H.


Static and Dynamic Libraries

In linux we can make two types of libraries: static and dynamic.

We could delete it and our program would still work, as it has a copy of everything you need. Only that part of the library that is needed is copied. For example, if the library has two functions and our program only calls one, only that function is copied.

When we have our executable and are running it, every time the code needs something from the library, it will go to this one. If we delete the library, our program will give an error that it does not find it.

What are the advantages and disadvantages of each of these types of libraries?

  • A program compiled with static libraries is larger, as it copies everything you need.
  • A program compiled with static libraries can be taken to another computer without the need to take the libraries.
  • A program compiled with static libraries is, in principle, faster in execution. When you call a library function, you have it in your code and you don’t have to go and read the dynamic library file to find the function and run it.
  • If we change a static library, executables are not affected. If we change a dynamic, executables are affected. This is an advantage if we have changed the library to fix an error (it is automatically fixed in all executables), but it is a drawback if touching that makes us change the executables (for example, we have added one more parameter to a library function, the executables already made stop working).


What kind of librarie do I use then?

It is as always a question of compromise between advantages and disadvantages. The dynamics are good for huge programs or for system libraries, which as they are on all linux computers, you don’t have to carry them around.

In unix the static libraries are usually called libname. a and the dynamic libname.so, where name is the name of our library.


Compiling and linking to static libraries

Once we have our code, to get a static library we must perform the following steps:

  • Get object files (.o) from all our sources (.c). To do this they are compiled with cc -c source. c -o source.o. The -c option tells the compiler not to create an executable, but only an object file. Here I put the cc compiler, because it is the one I used for the example, but it can be used gcc, or the g++ (for C++) or one of fortran, pascal, etc.
  • Create the library (.a). To do this we use the command ar with the following parameters: ar -rv libnombre. a fuente1.o fuente2.o ... The -r option tells the ar command to insert (or replace if already inside) the object files in the library. The -v option is "verbose", to display information while doing things. Below are all the object files we want. ar is actually a much more generic command than all this and serves to package any type of file (not just object files). You also have options to see what files are inside, delete some of them, replace them, etc.

Doing all this by hand each time can be a bit heavy. The usual thing is to make a Makefile file in the same directory where the sources of the library are and use make to compile it.

Makefile
CFLAGS=-I<path1> -I<path2> ...
libname.a: libname.a (objet1.o objet2.o ...)

In CLAGS you must put as many -Ipath> options as directories with files . h you need the library’s source to compile.

The library depends on the object files inside it. This is done by putting the library name and the object files in parentheses. There are some make verisones that only support an object file within the parentheses. It should be set then

libname.a: libname.a(objet1.o) libname.a(objet2.o) ...

We already have the library. Now, when compiling our program with the compiler, we must tell you where the libraries are and what they are. The compilation command would then be

$ cc -o myprogram myprogram.c -I<path1> -I<path2> ... -L<path1> -L<path2> ... -llibrary1 -llibrary2

The -I<path> are to indicate where are the necessary header files for the compilation (both in the program and in our libraries).

The -L<path> are to indicate the directories in which the libraries are located.

The -llibrary are to indicate that you should take that library. In the command we just put "library". The lib prefix and the extension . to is automatically set by the compiler.

There is one important detail to consider. Libraries should be set in such a way that the highest level is first and the lowest level at the end. That is, as we have it in the example, libreria1 can use libreria2 functions, but not the other way around. The reason is that when compiling libraries are read consecutively and loading from each of them only what is necessary. Let’s look at it with an example

Let’s assume that my program. or calls the library function1 and this function1 calls the library function2. The compiler reads my program.o. Since it needs functionality, it points to it as "necessary". Then it reads libreria1. It searches for the required functions, finds function1 and the load. As function1 calls function2, points function2 as necessary function. Then reads libreria2 and as function2 is necessary, the charge. All correct.

Let us now suppose that we have turned the order, that we have put -llibrary2 before -llibrary1. The compiler reads myprogram.c. As this requires functionality, it is pointed out as "necessary". It then reads libreria2. As function1 is not of this libraries and there are no more "necessary" functions (until now), ignores library2 and does not load any of it. It then reads library1, loads function1 and sees that it needs function2. Points2 function as necessary but ... the libraries are finished. An error of "linked2" is deleted in which it says that "I cannot find function2".

This also tells us that we have to have a certain order when designing libraries. We must make them clear that some can call others, but not the others, that is, organize them like a tree. Those at the top can call functions at the bottom, but not the other way around.

There is a small trap, but it is not very elegant. It consists of putting the same library several times in several positions. If in the case that it did not work we would have put again at the end -llibrary2, I would have compiled.

Compiling and "linking" with dynamic libraries

To compile the same files, but as a dynamic library, we have to follow the following steps:

  • Compile the sources, as before, to get the objects.
  • Create the library with the ld command. The options for this command would be ld -o liblibreria.so objeto1.o objeto2.o ... -shared. The -o liblibreria.so option indicates the name we want to give to the library. The -shared option tells you to make a library and not an executable (default option). object 1.o, object 2.o ... are the object files that we want to put in the library.

As before, doing this by hand can be heavy and a Makefile is usually done to compile with make. Like before, if you don’t know what I’m talking about, there’s the make’s paginilla. Unfortunately, the implicit rules don’t know how to make dynamic libraries (or, at least, I haven’t seen how), so we have to work a little more on the Makefile. It would look something like:

Makefile
liblibrary.so: objet1.c objet2.c ...
   cc -c -o objet1.o objet1.c
   cc -c -o objet2.o objet2.cMakefile
   ...
   ld -o liblibrary.so objet1.o objet2.o ... -shared
   rm objet1.o objet2.o ...
   ...
              
   rm objeto1.o objeto2.o ...

The library depends on the sources. They are compiled to get the . o (you would have to add the -I<path> options as necessary), build the library with ld and delete the generated objects. I have made the library dependent on the fonts to compile only if a font is changed. If I make it dependent on the objects, as in the end I delete them, the library would always be recompiled.

The ld command is more specific than ar, and I haven’t found options to modify or delete the objects inside the library. You have to build the entire library every time something is modified.

Once the library is generated, to link our program with it, you have to put:

cc -o myprogram myprogram.c -I<path1> -I<path2> ... -L<path1> -L<path2> ...  -Bdynamic -llibrary1 -llibrary2

The command is the same as the previous one for static libraries with the exception of -Bdynamic. It is quite common to generate both types of libraries simultaneously, so it is quite normal to find their static version and dynamic version from the same library. When compiling without option -Bdynamic can happen several things:

  • Liblibrary. a and liblibrary.so. Liblibrary is taken by default. a
  • There is only one of them. You take the one that exists.
  • There is none. Error.

The -Bdynamic option changes the first case, causing liblibreria.so to be held instead of liblibreria.a. The -Bdynamic option affects all the libraries that will fall in the compilation line. To change again, we can put -Bstatic at any time.

Once the executable is compiled, we need a last step. You have to tell the program, while it’s running, where the dynamic libraries are, since you’re going to look for them every time you call a function of them. We need to define the LD_LIBRARY_PATH environment variable, in which we put all directories where there are dynamic libraries of interest.

$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:<path1>:<path2>:<path3>
$                                                                                         export LD_LIBRARY_PATH

Being <path> the directories in which are the dynamic libraries. $LD_LIBRARY_PATH has been set to maintain its previous value and add new directories.

This blog was made by Roberto Palacios Cohort 11 at Holberton School Barranquilla

Thank you so much for everything.

To view or add a comment, sign in

Others also viewed

Explore topics