Compilation, Linking, and Library Construction in C

Computer Science · course

Browse all programmation documents

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

Advertisement

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

Advertisement

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

Advertisement

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

Advertisement

$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).