Compilation, édition de liens et construction de bibliothèques.
Un programme C est appelé programme source. Il vient sous forme d'un fichier se terminant par .c.
Il s'agit d'un fichier texte (ascii la plupart du temps).
Ce fichier texte ascii doit être converti en un programme écrit en langage machine (donc binaire)
exécutable par le microprocesseur.
Pour obtenir ce binaire exécutable (écrit en langage machine), plusieurs phases doivent être
accomplies.
Dans le monde du libre, notamment Linux, il existe un outil très puissant utilisé dans les différentes
phases menant à l'exécutable final : gcc (Gnu C Collection).
Cette commande Linux (paquetage gcc-numversion) admet un grand nombre d'options (man gcc).
Cette commande appliquée à un programme source a pour effet de générer un binaire exécutable
s'appelant a.out.
Exemple.
Soit le programme source hello.c :
#include <stdio.h>
int main (void)
{
/ afficher la chaîne Hello World et sauter une ligne /
printf("Hello World \n");
return 0;
}
Le programme source hello.c étant exempt d'erreurs (syntaxiques notamment), l'exécution de la commande
gcc hello.c aura donc comme résultat (final) le binaire exécutable a.out.
En réalité la collection gcc travaille en quatre étapes pour générer le binaire exécutable :
1. Passage au préprocesseur (preprocessing)
2. Compilation en langage assembleur (compiling)
3. Conversion du programme écrit en assembleur en programme écrit en langage machine (assembling)
4. Édition des liens (linking)
$ gcc hello.c
Preprocessing
compiling
assembling
linking
hello.c
hello.i
hello.s
hello.o
a.out
Par défaut, c'est-à-dire sans spécifier d'options, gcc efface tout fichier généré temporairement et ne
laisse, par conséquent que les fichiers correspondant au programme source et au binaire exécutable.
La fonction printf( ) est une fonction qui appartient à la bibliothèque standard du langage C. son
prototype est défini dans le fichier d'en-tête stdio.h (situé par défaut dans /usr/include).
Dans la suite on s'intéressera aux différentes étapes effectuées par gcc.
Étape 1 : Le prétraitement (Preprocessing)
$gcc -E hello.c > hello.i
Publicité
L'option –E passée à la commande gcc, permet d'interrompre le processus de compilation après la
première phase. Le résultat est un fichier texte (hello.i).
Le préprocesseur effectue essentiellement les opérations suivantes :
Inclusion des fichiers .h (fichiers d'en-tête) dans les programmes source.
·
· Suppression des commentaires (/ ………….. / ou // )
· Traitement des directives de définition et de compilation conditionnelle (#define, #undef,
#ifdef, #ifndef, #error, #else, #endif, #pragma).
Le préprocesseur contrôle la syntaxe du programme. En effet s'il y a eu erreur syntaxique le
préprocesseur va la signaler.
Les erreurs de syntaxe empêchent la génération du binaire exécutable ce qui n'est pas le cas pour les
avertissements (warnings). Par défaut, les messages d'avertissement ne sont pas affichés malgré leur
importance dans la résolution d'un bon nombre de problèmes (bugs).
Pour informer gcc qu'il doive tenir compte des avertissements il faut lui passer l'option -Wall
(Warning all).
Si on enlève la première ligne du programme hello.c et on appelle la commande gcc :
$ gcc -Wall hello.c
hello.c: In function `main':
hello.c:5: warning: implicit declaration of function `printf'
Étape 2 : Compilation en langage assembleur
Le programme source C est transformé en programme assembleur. Il suffit de passer à la commande
gcc l'option -S suivie du nom de fichier qui est issu du prétraitement pour obtenir la transformation
en langage assembleur.
Un fichier d'extension .s est automatiquement produit, c'est un fichier texte qui est lisible, mais
difficilement compréhensible si on ne connaît pas le langage symbolique du processeur (assembleur)
$gcc -S hello.i
Étape 3 : Conversion du programme écrit en assembleur en programme écrit en
langage machine (assembling)
Le programme assembleur qui est lisible est transformé en un programme machine binaire.
Il suffit de passer à la commande gcc l'option –c suivie du nom du fichier qui contient le code
assembleur pour obtenir un programme objet qui est un fichier d'extension .o.
Ce fichier binaire ne peut pas être directement édité et lu par un l'être humain car son format n'est
pas du texte. On peut, néanmoins, le rendre lisible en utilisant par exemple la commande od –x.
La commande od (octal dump) lit le fichier binaire et affiche sur la sortie standard les octets en
hexadécimal avec l'option -x (base 16, on compte de 0 à 15, les lettre a, b, c, d, e et f représentent 10,
11, 12, 13, 14, 15).
$gcc -c hello.s
Étape 4 : Édition des liens
Le fichier hello.o produit dans l'étape 3 est incomplet, en effet il ne contient pas le code de la
fonction printf( ). Ce code est situé dans une bibliothèque à part.
Afin que le programme puisse être compilé, nous avons inclus son prototype (#include <stdio.h>)
mais le code de la fonction n'est pas présent dans hello.o.
L'édition des liens va essayer de réunir le fichier objet et les fonctions contenues dans les
bibliothèques afin de construire le programme complet : le binaire exécutable a.out dans l'exemple.
$gcc hello.o
Publicité
REMARQUE IMPORTANTE
Attention, la compilation produit un code spécifique pour l'ordinateur (système d'exploitation). Un
exécutable d'un ordinateur sous Linux ne pourra pas être exécuté sur une station Sun faisant tourner
le système d'exploitation Solaris. Il faudra recompiler le programme pour pouvoir l'exécuter.
Sous Linux, il existe la commande ld (link editor) qui joue le rôle d'un éditeur de liens. Son rôle est
la production d'un binaire exécutable à partir de un ou plusieurs fichiers objets et éventuellement des
fichiers de bibliothèques (fichiers d'extension .a)
Construction de bibliothèques
Introduction.
Lorsqu'on réalise des programmes, il est fréquent que certaines fonctions ne soient pas spécifiques
au programme en cours. On a des bibliothèques qui contiennent des fonctions d'entrées/sorties
(printf, scanf, …) des fonctions pour la manipulation des chaînes de caractères (strlen, strcpy, ...) etc.
Bibliothèques statiques ou partagées ?
Lors d'une compilation, normalement on doit indiquer quelles sont les bibliothèques utilisées par le
programme afin de les inclure lors de la phase d'édition de liens.
Jusqu'ici nous ne l'avons pas fait car on écrit généralement des programmes qui appellent printf,
scanf, strcmp, strlen, … qui sont incluses dans la bibliothèque standard du langage C (glibc). Encore
une fois, il ne faut pas confondre bibliothèque (codes des fonctions) et fichier d'en-tête (prototypes
des fonctions).
Soit le programme source suivant qui calcule et affiche la racine carrée d'un réel passé comme
paramètre :
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
float nb;
if(argc != 2){
printf("usage : arrondir nb_reel\n");
exit(1);
}
nb = atof(argv[1]);
if (nb >= (float) 0)
{
printf("Racine carrée de %f : %f\n", sqrt(nb));
exit(0);
}
printf("Erreur ! Racine carrée non définie pour les réels négatifs");
exit(1);
}
Compilons le programme :
$gcc –Wall –o sqrt sqrt.c
/tmp/ccub132t.o(.text+0x45): In function `main' :
: référence indefinite vers « sqrt »
collect2 : ld a retourné 1 comme code d'état d'exécution
Publicité
La fonction mathématique sqrt( ) n'est pas trouvée lors de l'édition des liens (ld), aucun exécutable
n'a été généré.
Les fonctions mathématiques sont stockées dans une bibliothèque qui s'appelle libm (avec une
extension .a ou .so selon le type de la bibliothèque).
Lors de la compilation, l'option -lbibliotheque indique au compilateur de rechercher les symboles
dans la bibliothèque nommée libbibliotheque.so ou si elle n'existe pas dans libbibliotheque.a.
$./sqrt 45
La racine carrée de 45.000000 est 6.708204
Bibliothèque statique
Une bibliothèque statique se reconnaît par son extension en .a (archive), c'est un fichier qui peut
contenir un ou plusieurs fichiers objets (.o) et qui débute par un index. Il est possible de consulter
l'index d'une bibliothèque statique en utilisant la commande nm.
$nm –s /usr/lib/libutil.a
Le U indiquant que le symbole n'est pas défini dans l'archive, et le T qu'il est dans l'archive (par ex.
strcpy apparaîtrait ici comme U car il est utilisé mais appartient à une autre archive. L'option
--defined-only permet d'afficher uniquement les fonctions définies dans l'archive.
$nm –-defined-only /usr/lib/libutil.a
Avec des bibliothèques statiques, le code du fichier (des fichiers) est injecté dans le binaire
exécutable. De cette manière le binaire exécutable peut être exécuté sur un ordinateur qui peut ne pas
comporter la bibliothèque statique
Il devient donc possible de développer un binaire exécutable qui utilise une bibliothèque
commerciale achetée qu'on n'a pas le droit de diffuser. En éditant les liens avec la version statique,
on inclut le code nécessaire et l'application pourra être exécutée sur un ordinateur qui ne comporte
pas la bibliothèque.
Une édition de liens qui utilise des bibliothèques statiques produira un exécutable qui occupera
davantage d'espace disque. Lorsque les fonctions de la bibliothèque sont très utilisées on ne prendra
pas la version statique de la bibliothèque pour compiler.
Bibliothèque dynamique (ou partagée)
Lorsque la bibliothèque comporte l'extension .so suivie le plus souvent du numéro de sa version,
c'est une bibliothèque partagée (shared object). À l'édition des liens, une portion de code est insérée,
elle sera utilisée lorsque l'application sera exécutée pour lancer l'éditeur de liens dynamique.
Le code des fonctions ne sera donc plus dupliqué dans chaque exécutable, il sera présent dans un
seul fichier sur le disque. Lorsqu'on exécute, l'éditeur de liens dynamique localise les bibliothèques
partagées et les charge en mémoire.
L'exécutable occupera donc moins d'espace disque, et moins d'espace mémoire. En effet, si dix
applications qui utilisent les mêmes fonctions de bibliothèques partagées sont exécutées en même
temps, le code des fonctions est présent une seule fois dans la mémoire.
Rappelons cependant qu'il faut que la bibliothèque partagée soit présente sur l'ordinateur où vous
exécutez, d'autre part, son code est entièrement chargé en mémoire, il ne faut donc pas créer une
bibliothèque partagée dans laquelle une seule fonction est très souvent utilisée.
Utilisation des bibliothèques statiques
Une bibliothèque statique (archive) est une collection d'un ou de plusieurs fichiers objets. Les
fichiers objets sont générés individuellement à partir de la commande gcc avec l'option –c.
La commande ar (archiver) permet de créer l'archive à partir des fichiers objets.
$gcc –c fichier1.c è fichier1.o
Publicité
$gcc –c fichier2.c è fichier2.o
.
.
.
$gcc –c fichiern.c è fichiern.o
La création de l'archive libarchive.a à partir de fichier1.o, fichier2.o, …, fichiern.o peut se faire de la
manière suivante :
$ar r libarchive.a fichier1.o fichier2.o …fichiern.o
L'interrogation (consultation) de l'archive se fait de la manière suivante : $ar t libarchive.a
La commande ranlib génère l'index pour l'archive archive.a et stocke cet index dans l'archive. La
commande nm permet de vérifier quels sont les symboles définis et indéfinis dans l'archive.
$ranlib libarchive.a
$nm –s libarchive.a
Remarque.
Il faut que le nom de la bibliothèque (statique ou dynamique) doit commencer par le préfixe lib.
Utilisation de la bibliothèque statique
L'option -Ichemin_repertoire_include permet d'indiquer qu'il faut chercher les fichiers .h dans le
répertoire dont le chemin est précisé (en plus des répertoires qui sont parcourus habituellement), et
l'option -Lchemin_repertoire_bib a le même effet pour les bibliothèques.
$gcc F1.c F2.c … -Irép -Lrép -larchive –o binaire_exécutable
Utilisation des bibliothèques partagées
Il faut compiler séparément les différents fichiers source avec l'option –c avec éventuellement
l'option –fPIC (Position Independent Code). Ensuite il faut créer l'objet partagé avec l'option –shared
de la manière suivante.
$gcc –c –fPIC fichier1.c è fichier1.o
$gcc –c –fPIC fichier2.c è fichier2.o
.
.
.
$gcc –c –fPIC fichiern.c è fichiern.o
$gcc -shared -o libarchive.so fichier1.o fichier2.o … fichiern.o
Une bibliothèque partagée se place dans le répertoire /usr/lib, si vous n'avez pas les droits pour
le faire, vous pouvez placer votre bibliothèque partagée dans le répertoire lib que vous avez créé à la
racine de votre compte, lors de la compilation vous utiliserez l'option Wl,-rpath,$HOME/lib, ceci
aura pour effet d'ajouter le répertoire dont le chemin est précisé à la liste des répertoires dans
lesquels l'éditeur de liens dynamiques chercher les bibliothèques partagées. Le -Wl, indique qu'il faut
passer "-rpath $HOME/lib" à l'éditeur de liens (i.e. cette option est utilisée lors de la dernière étape
de la compilation).
La commande ldd permet de connaître les dépendances dynamiques de l'exécutable. libarchive.so
est la bibliothèque que nous avons créée et qui est dans le répertoire indiqué à droite de la flèche.
glibc est la bibliothèque standard C, et ld-linux.so est l'éditeur de lien dynamique.
L'utilitaire strip peut être utilisé pour réduire la taille de l'exécutable en éliminant tous les
symboles de la table des symboles. Donc il n'y aura plus d'informations pour la mise au point du
programme (débogage).