Bibliothèques¤

Une bibliothèque logicielle est un ensemble de fichiers proposant des fonctionnalités prêtes à l'emploi. L'appel à printf en est un exemple emblématique : la fonction est déclarée dans l'en-tête <stdio.h> et son implémentation est fournie par la bibliothèque standard libc.
Dans la pratique, l'anglicisme library est très courant, car il est bref et universel dans le milieu informatique. Il ne faut toutefois pas le confondre avec l'anglais bookstore, qui correspond à notre « librairie ». Les bibliothèques logicielles se composent le plus souvent d'un ou plusieurs fichiers binaires compilés pour une architecture donnée et d'en-têtes (headers) décrivant les fonctions disponibles.
Les exemples qui suivent sont tirés d'un environnement POSIX. Sous Windows, la procédure diffère légèrement, mais les concepts restent les mêmes : une bibliothèque expose des fonctions ou des structures de données, et un programme les relie lors de l'édition des liens.
Exemple : libgmp¤
libgmp fournit des fonctions d'arithmétique multiprécision très utilisées. En consultant le paquet Debian, on constate qu'il existe des variantes pour plusieurs architectures (amd64, arm64, s390x, i386, etc.). Un ordinateur de bureau x86_64 nécessitera la variante amd64, tandis qu'un Raspberry Pi demandera arm64. La liste des fichiers installés pour l'architecture choisie comprend notamment :
# 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 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 y retrouve donc :
gmp.h- Fichier d'en-tête à inclure pour utiliser les fonctionnalités.
libgmp.a- Bibliothèque statique contenant l'implémentation en langage machine des fonctions. Elle doit être mentionnée explicitement lors de l'édition des liens.
libgmp.so- Bibliothèque dynamique qui contient elle aussi le code machine, mais qui sera chargée au moment de l'exécution.
Supposons que l'on souhaite calculer des orbites pour un satellite d'observation de Jupiter. Pour apprivoiser cette library, on écrit :
#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);
}
Première tentative de compilation :
$ gcc gmp.c
gmp.c:1:10: fatal error: gmp.h: No such file or directory
#include <gmp.h>
^~~~~~~
compilation terminated.
La bibliothèque n'est pas installée sur la machine. On l'installe alors :
Nouvelle compilation :
$ 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, le compilateur a bien produit un fichier objet, mais l'éditeur de liens ne sait pas où trouver les symboles, comme __gmpz_add_ui. Il suffit de préciser la bibliothèque à charger :
$ gcc gmp.c -lgmp
$ ./a.out
19810983098510928501928599999999999990
19810983098510928501928600000000000002
392475051329485669436248957939688603493163430354043714007714400000000000004
La commande ci-dessus utilise libgmp.so, la version dynamique. L'exécutable obtenu reste donc dépendant de la présence de libgmp sur la machine cible. Si l'on transmet le binaire à une personne qui n'a pas installé la bibliothèque, l'exécution échouera.
On peut au contraire créer un exécutable autonome en liant explicitement la bibliothèque statique :
Dans ce cas, tout le code nécessaire est intégré dans l'exécutable et il suffit de partager ce fichier avec quelqu'un utilisant la même architecture. Le revers de la médaille est la taille plus importante du binaire :
# ~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
2752 680 16 3448 d78 ./a.out
Exemple : ncurses¤
La bibliothèque ncurses, évolution de curses conçue par Ken Arnold, permet de créer des interfaces textuelles riches. Elle offre le positionnement arbitraire du curseur, le dessin de fenêtres, de menus, d'ombres ou encore de jeux de couleurs.
Figure : Exemple d'interface graphique écrite avec ncurses. Ici, la configuration du noyau Linux.

Un programme minimal peut ressembler à ceci :
#include <ncurses.h>
int main(void)
{
initscr(); // Démarrer ncurses
printw("hello, world"); // Afficher un message
refresh(); // Mettre à jour l'écran réel
getch(); // Attendre une frappe
endwin(); // Quitter ncurses proprement
return 0;
}
La compilation n'aboutit que si la bibliothèque est installée et si le lien est mentionné :
Bibliothèques statiques¤
Une static library est une archive d'objets compilés pour une architecture donnée. On rencontre les extensions suivantes :
Pour illustrer la création d'une bibliothèque statique, reprenons le chiffrement de César. On écrit d'abord 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;
}
}
}
et le fichier d'en-tête correspondant :
/**
* Function that compute the Caesar cipher
* @param str input string
* @param key offset to add to each character
*/
void caesar(char str[], unsigned key);
La création de la bibliothèque se déroule en deux étapes : génération de l'objet, puis archivage :
On peut ensuite écrire un programme utilisateur :
#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]);
}
}
La compilation se fait en précisant où trouver les en-têtes (-I.) et la bibliothèque (-L.) :
Sous Windows, les outils diffèrent et la procédure demande quelques étapes supplémentaires, que nous n'abordons pas ici.
Bibliothèques dynamiques¤
Une dynamic library est, elle aussi, un binaire compilé pour une architecture donnée. Elle porte en général l'extension :
.sosur les systèmes POSIX ;.dllsous Windows.
Son avantage principal est de partager le code entre plusieurs exécutables, réduisant l'espace disque et la mémoire consommée. En contrepartie, l'utilisateur doit disposer de la bibliothèque au moment de l'exécution. Sous un environnement POSIX, les bibliothèques dynamiques sont stockées dans des répertoires dédiés et référencées par l'éditeur de liens. Sous Windows, l'organisation est moins uniformisée et chaque application peut embarquer sa propre copie des .dll.
En reprenant l'exemple du chiffrement de César, on produit d'abord la bibliothèque dynamique :
Pour compiler le programme, il faut indiquer les chemins dans lesquels chercher les bibliothèques. Comme nous ne souhaitons pas installer libcaesar.so globalement, nous ajoutons le répertoire courant à la variable d'environnement LIBRARY_PATH au moment de la compilation :
À l'exécution, la même logique s'applique avec LD_LIBRARY_PATH, afin que le système sache où charger la bibliothèque :
En omettant cette variable, on obtient l'erreur suivante :