Bibliothèques¤
Une bibliothèque informatique est une collection de fichiers comportant des fonctionnalités logicielles prêtes à l'emploi. La printf
est une de ces fonctionnalités et offerte par le header <stdio.h>
faisant partie de la bibliothèque libc6
.
L'anglicisme library, plus court à prononcer et à écrire est souvent utilisé en lieu et place de bibliothèque tant il est omniprésent dans le monde logiciel. Le terme <stdlib.h>
étant la concaténation de standard library par exemple. Notez que librairie n'est pas la traduction correcte de library qui est un faux ami.
Une library, à l'instar d'une bibliothèque, contient du contenu (livre écrit dans une langue donnée) et un index (registre). En informatique il s'agit d'un fichier binaire compilé pour une architecture donnée ainsi qu'un ou plusieurs fichiers d'en-tête (header) contenant les définitions de cette bibliothèque.
Dans ce chapitre on donnera plusieurs exemples sur un environnement POSIX. Sous Windows, les procédures choses sont plus compliquées, mais les concepts restent les mêmes.
Exemple : libgmp¤
Voyons ensemble le cas de libgmp. Il s'agit d'une bibliothèque de fonctionnalités très utilisée et permettant le calcul arithmétique multiprécision en C. En observant le détail du paquet logiciel Debian on peut lire que libgmp
est disponible pour différentes architectures amd64
, arm64
, s390x
, i386
, ... Un développement sur un Raspberry-PI nécessitera arm64
alors qu'un développement sur un PC utilisera amd64
. En cliquant sur l'architecture désirée on peut voir que ce paquet se compose des fichiers suivants (list réduite aux fichiers concernant C):
# Fichier d'en-tête C
/usr/include/x86_64-linux-gnu/gmp.h
# Bibliothèque compilée pour l'architecture visée (ici amd64)
/usr/lib/x86_64-linux-gnu/libgmp.a
/usr/lib/x86_64-linux-gnu/libgmp.so
# Documentation de la libgmp
/usr/share/doc/libgmp-dev/AUTHORS
/usr/share/doc/libgmp-dev/README
/usr/share/doc/libgmp-dev/changelog.gz
/usr/share/doc/libgmp-dev/copyright
On a donc :
gmp.h
-
Fichier d'en-tête à include dans un fichier source pour utiliser les fonctionnalités
libgmp.a
-
Bibliothèque statique qui contient l'implémentation en langage machine des fonctionnalités à référer au linker lors de la compilation
libgmp.so
-
Bibliothèque dynamique qui contient aussi l'implémentation en langage machine des fonctionnalités
Imaginons que l'on souhaite bénéficier des fonctionnalités de cette bibliothèque pour le calcul d'orbites pour un satellite d'observation de Jupyter. Pour prendre en main cet libary on écrit ceci :
#include <gmp.h>
#include <stdio.h>
int main(void)
{
unsigned int radix = 10;
char a[] = "19810983098510928501928599999999999990";
mpz_t n;
mpz_init(n);
mpz_set_ui(n, 0);
mpz_set_str(n, a, radix);
mpz_out_str(stdout, radix, n);
putchar('\n');
mpz_add_ui(n, n, 12); // Addition
mpz_out_str(stdout, radix, n);
putchar('\n');
mpz_mul(n, n, n); // Square
mpz_out_str(stdout, radix, n);
putchar('\n');
mpz_clear(n);
}
Puis on compile :
$ gcc gmp.c
gmp.c:1:10: fatal error: gmp.h: No such file or directory
#include <gmp.h>
^~~~~~~
compilation terminated.
Aïe ! La bibliothèque n'est pas installée...
Pour l'installer, cela dépend de votre système d'exploitation :
Deuxième tentative :
$ gcc gmp.c
/tmp/cc2FxDSy.o: In function `main':
gmp.c:(.text+0x6f): undefined reference to `__gmpz_init'
gmp.c:(.text+0x80): undefined reference to `__gmpz_set_ui'
gmp.c:(.text+0x96): undefined reference to `__gmpz_set_str'
gmp.c:(.text+0xb3): undefined reference to `__gmpz_out_str'
gmp.c:(.text+0xd5): undefined reference to `__gmpz_add_ui'
gmp.c:(.text+0xf2): undefined reference to `__gmpz_out_str'
gmp.c:(.text+0x113): undefined reference to `__gmpz_mul'
gmp.c:(.text+0x130): undefined reference to `__gmpz_out_str'
gmp.c:(.text+0x146): undefined reference to `__gmpz_clear'
collect2: error: ld returned 1 exit status
Cette fois-ci on peut lire que le compilateur à fait sont travail, mais ne parvient pas à trouver les symboles des fonctions que l'on utilise p.ex. __gmpz_add_ui
. C'est normal parce que l'on n'a pas renseigné la bibliothèque à utiliser.
$ gcc gmp.c -lgmp
$ ./a.out
19810983098510928501928599999999999990
19810983098510928501928600000000000002
392475051329485669436248957939688603493163430354043714007714400000000000004
Cette manière de faire utilise le fichier libgmp.so
qui est la bibliothèque dynamique, c'est-à-dire que ce fichier est nécessaire pour que le programme puisse fonctionner. Si je donne mon exécutable à un ami qui n'as pas install libgmp sur son ordinateur, il ne sera pas capable de l'exécuter.
Alternativement on peut compiler le même programme en utilisant la librairie statique
C'est-à-dire qu'à la compilation toutes les fonctionnalités ont été intégrées à l'exécutable et il ne dépend de plus rien d'autre que le système d'exploitation. Je peux prendre ce fichier le donner à quelqu'un qui utilise la même architecture et il pourra l'exécuter. En revanche, la taille du programme est plus grosse :
# ~167 KiB
$ gcc gmp.c -l:libgmp.a
$ size a.out
text data bss dec hex filename
155494 808 56 156358 262c6 ./a.out
# ~8.5 KiB
$ gcc gmp.c -lgmp
$ size a.out
text data bss dec hex filename
2752 680 16 3448 d78 ./a.out
Exemple : ncurses¤
La bibliothèque ncurses traduction de nouvelles malédictions est une évolution de curses développé originellement par Ken Arnold . Il s'agit d'une bibliothèque pour la création d'interfaces graphique en ligne de commande, toujours très utilisée.
La bibliothèque permet le positionnement arbitraire dans la fenêtre de commande, le dessin de fenêtres, de menus, d'ombrage sous les fenêtres, de couleurs ...
L'écriture d'un programme Hello World avec cette bibliothèque pourrait être :
#include <ncurses.h>
int main()
{
initscr(); // Start curses mode
printw("hello, world"); // Print Hello World
refresh(); // Print it on to the real screen
getch(); // Wait for user input
endwin(); // End curses mode
return 0;
}
La compilation n'est possible que si :
- La bibliothèque est installée sur l'ordinateur
- Le lien vers la bibliothèque dynamique est mentionné à la compilation
Bibliothèques statiques¤
Une static library est un fichier binaire compilé pour une architecture donnée et portant les extensions :
Une bibliothèque statique n'est rien d'autre qu'une archive d’un ou plusieurs objets. Rappelons-le un objet est le résultat d'une compilation.
Par exemple si l'on souhaite écrire une bibliothèque statique pour le code de César on écrira un fichier source caesar.c
:
void caesar(char str[], unsigned key)
{
key %= 26;
for (int i = 0; str[i]; i++)
{
char c = str[i];
if (c >= 'a' && c <= 'z')
{
str[i] = ((c + key > 'z') ? c - 'z' + 'a' - 1 : c) + key;
}
else if (c >= 'A' && c <= 'Z')
{
str[i] = ((c + key > 'Z') ? c - 'Z' + 'A' - 1 : c) + key;
}
}
}
Ainsi qu'un fichier d'en-tête caesar.h
:
/**
* Function that compute the Caesar cipher
* @param str input string
* @param key offset to add to each character
*/
void caesar(char str[], unsigned key);
Pour créer une bibliothèque statique rien de plus facile. Le compilateur crée l'objet, l'archiver crée l'amalgame :
Puis il suffit d'écrire un programme pour utiliser cette bibliothèque :
#include <caesar.h>
#include <stdio.h>
#define KEY 13
int main(int argc, char *argv[])
{
for (int i = 1; i < argc; i++)
{
caesar(argv[i], KEY);
printf("%s\n", argv[i]);
}
}
Et de compiler le tout. Ici on utilise -I.
et -L.
pour dire au compilateur de chercher le fichier d'en-tête et la bibliothèque dans le répertoire courant.
La procédure sous Windows est plus compliquée et ne sera pas décrite ici.
Bibliothèques dynamiques¤
Une dynamic library est un fichier binaire compilé pour une architecture donnée et portant les extensions :
L'avantage principal est de ne pas charger pour rien chaque exécutable compilé de fonctionnalités qui pourraient très bien être partagées. L'inconvénient est que l'utilisateur du programme doit impérativement avoir installé la bibliothèque. Dans un environnement POSIX les bibliothèques dynamiques disposent d'un emplacement spécifique ou elles sont toute stockées. Malheureusement sous Windows le consensus est plus partagé et il n'est pas rare de voir plusieurs applications différentes héberger une copie des dll localement si bien que l'avantage de la bibliothèque dynamique est anéanti par un défaut de cohérence.
Reprenant l'exemple de César vu plus haut, on peut créer une bibliothèque dynamique :
Puis compiler notre programme pour utiliser cette bibliothèque. Avec une bibliothèque dynamique, il faut spécifier au compilateur quels sont les chemins vers lesquels il pourra trouver les bibliothèques installées. Comme ici on ne souhaite pas installer la bibliothèque et la rendre disponible pour tous les programmes, il faut ajouter aux chemins par défaut, le chemin local $(pwd .)
, en créant une variable d'environnement nommée LIBRARY_PATH
.
Le problème est identique à l'exécution, car il faut spécifier (ici avec LD_LIBRARY_PATH
) le chemin ou le système d'exploitation s'attendra à trouver la bibliothèque.
Car sinon c'est l'erreur :