Compilation, Linking, and Library Construction in C
Ce document présente les différentes étapes de la compilation d'un programme en langage C, ainsi que la construction et l'utilisation des bibliothèques statiques et partagées. Il s'adresse aux étudiants et développeurs souhaitant comprendre le processus complet menant à la création d'un exécutable, ainsi que la gestion des bibliothèques dans un environnement Linux avec l'outil gcc.
D'après le document Compilation, Linking, and Library Construction in C
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.
Document source
Computer Science · PDF · 8 pages
Afficher l'aperçu du document
Ce document présente les différentes étapes de la compilation d'un programme en langage C, ainsi que la construction et l'utilisation des bibliothèques statiques et partagées. Il s'adresse aux étudiants et développeurs souhaitant comprendre le processus complet menant à la création d'un exécutable, ainsi que la gestion des bibliothèques dans un environnement Linux avec l'outil gcc.
Phases de la compilation d'un programme C
Un programme C, appelé programme source, est un fichier texte généralement avec l'extension .c. Ce fichier doit être transformé en un programme exécutable écrit en langage machine. Cette transformation s'effectue en plusieurs étapes successives, réalisées par la commande gcc sous Linux :
- Prétraitement (Preprocessing)
- Compilation en langage assembleur (Compiling)
- Assemblage en langage machine (Assembling)
- Édition des liens (Linking)
Par défaut, la commande gcc hello.c produit un exécutable nommé a.out. Les fichiers temporaires générés lors des étapes intermédiaires sont supprimés automatiquement.
#include <stdio.h>
int main (void)
{
/* afficher la chaîne Hello World et sauter une ligne */
printf("Hello World \n");
return 0;
}
Étape 1 : Le prétraitement
Le préprocesseur traite les directives du langage C avant la compilation proprement dite. Il inclut notamment :
- L'inclusion des fichiers d'en-tête
.h - La suppression des commentaires
- Le traitement des directives conditionnelles (
#define,#ifdef, etc.)
Pour interrompre le processus après cette étape et obtenir le fichier prétraité, on utilise :
gcc -E hello.c > hello.i
Le fichier hello.i est un fichier texte contenant le code source après prétraitement.
Le préprocesseur signale les erreurs de syntaxe qui empêchent la compilation. Les avertissements (warnings) ne sont pas affichés par défaut, mais peuvent être activés avec l'option -Wall :
gcc -Wall hello.c
Par exemple, si on enlève la ligne #include <stdio.h> dans hello.c, gcc affichera un avertissement indiquant une déclaration implicite de la fonction printf.
Étape 2 : Compilation en langage assembleur
Le code source prétraité est transformé en code assembleur, lisible mais spécifique au processeur. Pour obtenir ce fichier, on utilise :
gcc -S hello.i
Le fichier généré porte l'extension .s.
Étape 3 : Assemblage en langage machine
Le code assembleur est converti en code machine binaire, contenu dans un fichier objet avec extension .o. Cette étape s'effectue avec :
gcc -c hello.s
Le fichier objet est un fichier binaire non lisible directement, mais on peut en afficher le contenu hexadécimal avec la commande :
od -x hello.o
Étape 4 : Édition des liens (linking)
Le fichier objet hello.o est incomplet car il ne contient pas le code des fonctions externes comme printf(), qui se trouve dans une bibliothèque standard. L'édition des liens réunit les fichiers objets et les bibliothèques nécessaires pour produire l'exécutable final :
gcc hello.o
Cette étape produit le fichier exécutable a.out.
Remarque : Un exécutable compilé pour un système d'exploitation donné (ex. Linux) ne fonctionnera pas sur un autre (ex. Solaris) sans recompilation.
La commande ld sous Linux est un éditeur de liens qui réalise cette étape en combinant fichiers objets et bibliothèques.
Construction et utilisation des bibliothèques
Les bibliothèques regroupent des fonctions réutilisables, comme celles d'entrée/sortie (printf, scanf) ou de manipulation de chaînes (strlen, strcpy).
Il existe deux types principaux de bibliothèques :
- Bibliothèques statiques (.a)
- Bibliothèques dynamiques ou partagées (.so)
Exemple d'utilisation d'une fonction mathématique
Considérons un programme qui calcule la racine carrée d'un nombre réel passé en argument :
#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", nb, sqrt(nb));
exit(0);
}
printf("Erreur ! Racine carrée non définie pour les réels négatifs");
exit(1);
}
La compilation simple :
gcc -Wall -o sqrt sqrt.c
génère une erreur d'édition des liens car la fonction sqrt() n'est pas trouvée. Elle se trouve dans la bibliothèque mathématique libm. Il faut donc indiquer explicitement cette bibliothèque :
gcc -Wall -o sqrt sqrt.c -lm
L'exécutable produit fonctionne alors correctement :
$ ./sqrt 45
Racine carrée de 45.000000 : 6.708204
Bibliothèque statique
Une bibliothèque statique est un fichier archive .a contenant un ou plusieurs fichiers objets .o. Elle commence par un index qui peut être consulté avec la commande :
nm -s /usr/lib/libutil.a
Les symboles sont marqués :
U: symbole non défini dans l'archive (utilisé mais défini ailleurs)T: symbole défini dans l'archive
On peut afficher uniquement les symboles définis avec :
nm --defined-only /usr/lib/libutil.a
Lors de l'édition des liens avec une bibliothèque statique, le code des fonctions est injecté dans l'exécutable. Cela permet d'exécuter le programme sur un système ne possédant pas la bibliothèque. Cependant, cela augmente la taille de l'exécutable.
Bibliothèque dynamique (partagée)
Une bibliothèque partagée porte l'extension .so (shared object). Lors de l'édition des liens, une portion de code est insérée dans l'exécutable pour lancer l'éditeur de liens dynamique au moment de l'exécution.
Le code des fonctions n'est pas dupliqué dans chaque exécutable, mais chargé une seule fois en mémoire, ce qui économise espace disque et mémoire lorsque plusieurs programmes utilisent la même bibliothèque.
Attention, la bibliothèque partagée doit être présente sur la machine d'exécution. De plus, il est déconseillé de créer une bibliothèque partagée contenant une seule fonction très utilisée, car le chargement dynamique peut alors être inefficace.
Création et utilisation des bibliothèques
Création d'une bibliothèque statique
Les fichiers objets sont générés individuellement avec :
gcc -c fichier1.c # génère fichier1.o
gcc -c fichier2.c # génère fichier2.o
...
gcc -c fichiern.c # génère fichiern.o
La bibliothèque statique libarchive.a est créée avec la commande :
ar r libarchive.a fichier1.o fichier2.o ... fichiern.o
On peut consulter le contenu de l'archive avec :
ar t libarchive.a
La commande ranlib génère l'index de la bibliothèque :
ranlib libarchive.a
La commande nm permet de vérifier les symboles définis et indéfinis :
nm -s libarchive.a
Remarque : Le nom d'une bibliothèque doit commencer par le préfixe lib.
Utilisation d'une bibliothèque statique
Lors de la compilation, on peut indiquer les répertoires où chercher les fichiers d'en-tête et les bibliothèques avec :
-Ichemin_repertoire_includepour les fichiers.h-Lchemin_repertoire_bibpour les bibliothèques
La bibliothèque est liée avec l'option -larchive (sans le préfixe lib ni l'extension). Par exemple :
gcc F1.c F2.c ... -Irép -Lrép -larchive -o binaire_exécutable
Création d'une bibliothèque partagée
Les fichiers objets doivent être compilés avec les options -c et -fPIC (Position Independent Code) :
gcc -c -fPIC fichier1.c # génère fichier1.o
gcc -c -fPIC fichier2.c # génère fichier2.o
...
gcc -c -fPIC fichiern.c # génère fichiern.o
La bibliothèque partagée est créée avec :
gcc -shared -o libarchive.so fichier1.o fichier2.o ... fichiern.o
La bibliothèque partagée est généralement placée dans /usr/lib. Si vous n'avez pas les droits, vous pouvez la placer dans un répertoire personnel, par exemple $HOME/lib. Pour que l'éditeur de liens dynamique la trouve, ajoutez l'option :
-Wl,-rpath,$HOME/lib
lors de la compilation finale. L'option -Wl, indique que l'option suivante est transmise à l'éditeur de liens.
Vérification des dépendances dynamiques
La commande ldd permet d'afficher les bibliothèques partagées utilisées par un exécutable :
ldd binaire_exécutable
Elle liste les bibliothèques partagées, leur emplacement, ainsi que l'éditeur de liens dynamique (ld-linux.so).
Réduction de la taille des exécutables
La commande strip supprime les symboles de la table des symboles d'un exécutable, réduisant ainsi sa taille. Cependant, cela supprime aussi les informations nécessaires au débogage.
Glossaire des termes clés
- Programme source : fichier texte contenant le code en langage C, généralement avec extension
.c. - Préprocesseur : étape qui traite les directives
#include,#define, supprime les commentaires et gère la compilation conditionnelle. - Fichier objet : fichier binaire intermédiaire avec extension
.o, produit après assemblage du code assembleur. - Édition des liens (linking) : étape qui combine les fichiers objets et les bibliothèques pour produire un exécutable.
- Bibliothèque statique : archive
.acontenant des fichiers objets, dont le code est injecté dans l'exécutable lors de la compilation. - Bibliothèque partagée (dynamique) : fichier
.sochargé en mémoire au moment de l'exécution, partagé entre plusieurs programmes. - gcc : compilateur C sous Linux, qui réalise toutes les étapes de compilation et d'édition des liens.
- ar : utilitaire pour créer et manipuler des archives (bibliothèques statiques).
- ranlib : commande qui génère un index dans une archive pour accélérer l'édition des liens.
- nm : outil pour afficher les symboles définis et indéfinis dans un fichier objet ou une bibliothèque.
- ldd : commande qui affiche les bibliothèques partagées utilisées par un exécutable.
- strip : utilitaire pour supprimer les symboles de débogage d'un exécutable afin de réduire sa taille.
- Position Independent Code (PIC) : code compilé de manière à pouvoir être chargé à n'importe quelle adresse mémoire, nécessaire pour les bibliothèques partagées.
Points clés à retenir
- La compilation d'un programme C se fait en quatre étapes : prétraitement, compilation en assembleur, assemblage en code machine, et édition des liens.
- Le préprocesseur gère l'inclusion des fichiers d'en-tête, la suppression des commentaires et les directives conditionnelles.
- La fonction
printfappartient à la bibliothèque standard C, dont le prototype est déclaré dansstdio.h. - Les bibliothèques statiques (.a) sont intégrées dans l'exécutable, augmentant sa taille mais assurant son indépendance.
- Les bibliothèques partagées (.so) sont chargées au moment de l'exécution et permettent un partage du code entre plusieurs programmes.
- Pour utiliser une bibliothèque externe, il faut souvent indiquer explicitement son inclusion lors de l'édition des liens avec l'option
-l. - La compilation et l'exécution d'un programme sont spécifiques au système d'exploitation et à l'architecture matérielle.
- Les outils
ar,ranlib,nm,lddetstripsont essentiels pour gérer les bibliothèques et les exécutables.
Commentaires
Aucun commentaire pour le moment. Posez la première question.