Fiche 1
Compilation et directives de
préprocesseur
1.1 Description détaillée du processus de compilation
Le processus de compilation se décompose en diérentes phases conduisant à la construction d’un
chier binaire exécutable. Ces phases ne sont, en général, pas spéciques au C++ et quand bien
même les diérents outils de programmation peuvent les cacher, le processus de génération des
exécutables se déroule toujours selon les principes suivant.
La première étape appelée précompilation, consiste à “traiter” les chiers sources (chiers d’en-
tête: *.h ou *.hh et chiers contenant le code à proprement dit: *.cc *.cpp) avant compilation.
Dans le cas du C et du C++ , il s’agit des opérations eectuées par le préprocesseur (remplacement
de macros, suppression de texte, inclusion de chiers…). Le résultat de la précompilation peut
s’obtenir via la commande suivante
$ g++ -E fichier_source.cpp
À la précompilation succède la compilation séparée qui est le fait de compiler séparément les
chiers sources. Le résultat de la compilation d’un chier source est généralement un chier en
assembleur, soit le langage décrivant les instructions du microprocesseur de la machine cible pour
laquelle le programme est destiné. Cette étape conduit à la création de chiers objets (d’extension
.o) contenant la traduction du code assembleur en langage machine. Les données initialisées,
par exemple, sont également comprises dans les chiers objets. Pour ce qui concerne le C++ , la
commande nécessaire à la compilation séparée s’écrit :
$ g++ -c fichier_source.cpp
Cette opération créera, par conséquent, un chier objet nommé fichier_source.o. L’ensemble
des chiers objets relatifs à un projet ou code donné peuvent être regroupés en une librairie
(statique ou dynamique) an de rassembler dans un même chier les fonctionnalités développées.
L’étape nale du processus de compilation consiste à regrouper la totalité des données de
même que les chiers objets et les bibliothèques (fonctions de la bibliothèque standard et des
autres bibliothèques externes) ainsi qu’à résoudre les références inter-chiers. Cette étape est
appelée édition de liens (linking en anglais). Le résultat de l’édition de liens est un chier image
qui pourra être chargé en mémoire par le système d’exploitation. Les chiers exécutables et
les bibliothèques dynamiques sont des exemples de chiers images. La commande relative à ce
mécanisme est la suivante:
$ g++ fichier_source1.o fichier_source2.o … -o fichier_executable
Comme nous le soulignions en introduction, certains compilateurs peuvent réaliser l’ensemble
de ces étapes en une seule opération. Le compilateur C++ peut ainsi compiler séparément l’ensemble
des chiers sources et réaliser l’édition de liens via la commande
$ g++ fichier_source1.cpp fichier_source2.cpp … -o fichier_executable
1.2 Options de compilation
Le compilateur C++ autorise l’utilisation d’une multitude d’options concernant aussi bien l’optimisation
du processus de compilation que la compréhension et la résolution des éventuels conits. L’ensemble
des options disponibles pour la commande g++ peut être obtenu à l’aide de l’instruction man g++a.
Parmi les multiples options disponiblesb, la liste ci-dessous recense les plus couramment utilisées:
l’option -Ichemin indique au compilateur où sont localisés les chiers d’entêtes nécessaires
à la compilation séparée du programme. Par défaut, le compilateur recherche ces chiers
localement et dans le répertoire /usr/include,
l’option -Lchemin est essentiel lors du processus d’édition de liens an d’indiquer au compi-
lateur où se situent les librairies externes. Soit le chemin d’accès est complet et pointe donc
vers la librairie concernée, soit le chemin d’accès indique uniquement le répertoire où sont
localisées les librairies, auquel cas il est nécessaire de préciser le nom de la librairie à utiliser
via l’option -lnom_librairie,
l’option -W permet l’activation des messages de mise en garde (le Wse référant à l’expression
warning). Ces warnings n’empècheront pas l’exécution du programme nal mais fournissent
des indications voir des conseils en relation avec certaines parties du code. Diérents niveaux
de mise en garde sont accessibles, le plus complet étant -Wall -Wextra,
l’option -Didentificateur permet la dénition d’identicateur à fournir lors du processus de
précompilation. L’activation des directives de préprocesseur peut se faire par ce biais ce
qui permettra au compilateur d’insérer, de supprimer ou de modier les portions de code
relatives à cette directive. À titre d’exemple, il pourra s’avérer utile lors d’une phase de
débogage d’un programme, de dénir un identicateur __DEBUG__ qui achera des informa-
tions complémentaires lors de l’exécution du programme. An d’inclure ces instructions
supplémentaires au sein du code, la commande de compilation deviendra g++ -D__DEBUG__
...,
an de proter des processeurs multi-cœurs présents dans les machines récentes, l’option -On
permet l’optimisation de la compilation en répartissant la charge de compilation sur ncœurs.
1.3 Directives de préprocesseur
Comme nous l’avons rappelé en introduction à cette annexe, la compilation d’un programme se
déroule en trois phases. La première est exécutée par le préprocesseur et vise à remplacer les
directives de compilation par des instructions C++ . Dans les faits, le préprocesseur recherche des
directives de compilation repérées en début de ligne par le symbole #et se terminant avec la n
de la ligne. La syntaxe est ainsi
#directive [paramètres]
aman, pour manual, constitue l’instruction unix permettant de connaître les diérents modes de fonctionnement
d’une commande donnée
bpour plus d’informations, le site internet http://www.antoarts.com/the-most-useful-gcc-options-and-extensions/ pro-
pose une liste des options de g++ les plus utiles.
2
Il est possible de placer des espaces blancs avant et après la directive mais, contrairement au
compilateur, les sauts de lignes et les commentaires ne sont pas considérés comme des blancs par
le préprocesseur. Par conséquent, on ne doit pas couper une ligne de directive, ni y placer un
commentaire qui pourrait entrer en conit avec la directive. Notons qu’il ne faut pas de point
virgule en n de ligne.
Si la directive ne tient pas sur une seule ligne, il sut d’écrire le caractère \juste avant le saut
de ligne; dans ce cas, la ligne courante est considérée comme la suite de la précédente. Rappelons
que ceci est également vrai pour le compilateur qui ignore les paires \+ saut de ligne.
Parmi les diérents types de directives de préprocesseur, la plus fréquemment utilisée demeure
la directive d’inclusion #include. Elle indique au préprocesseur de remplacer la ligne courante par
l’ensemble des lignes du chier donné en paramètre. En pratique, cette directive est essentiellement
utilisée pour inclure les chiers d’entête de librairies (chiers *.h ou *.hh).
La directive #define identificateur permet de dénir un paramètre “identicateur” qui pourra
être utilisé dans une clause conditionnelle (#if,#ifdef,#ifndef voir ci-après). L’identicateur ne
doit pas commencer par un chire.
Par ailleurs, il est possible de contrôler ce qui sera compilé eectivement ou non, avec les
clauses de condition. Si l’on écrit
#if condition
...
#endif
la condition, qui doit être une constante numérique au format C++ , est évaluée par le prépro-
cesseur; si la condition est non nulle, la clause #if est ignorée et le code compris dans la séquence
#if #endif est considéré par le compilateur. En revanche, si la condition est nulle, tout ce qui se
trouve entre les directives #if et #endif est ignoré et donc non compilé. La clause #if peut avoir
une clause #else voir #elif (pour else if).
Lorsque l’activation des directives de préprocesseur se fait au moyen de l’option de compilation
-Didentificateur (cf plus haut), on aura une syntaxe telle que l’illustre l’exemple suivant
test_debug.cpp
#include <iostream>
using namespace std;
int main()
{
#if (DEBUG == 1)
cout << "DEBUG: "
<< "Mode debug du programme" << endl;
#else
cout << "NOTICE: "
<< "Mode normal du programme" << endl;
#endif
}
compilation :
$ g++ -DDEBUG=1 test_debug.cpp -o test_debug
Dans le cas d’identicateur tel que déni via la directive #define, on utilisera l’écriture suivante
#ifdef identificateur
...
#endif
ou sa négation
#ifndef identificateur
...
#endif
3
Enn, les clauses de compilation conditionnelles peuvent être imbriquées.
La directive #define sert également à la dénition des macros. Il s’agit d’abréviations ou de
noms symboliques, traditionnellement en majuscules, se référant à d’autres objets tels qu’une
constante numérique ou une chaîne de caractères. Les macros suivantes constituent des exemples
classiques utilisés notamment en C:
#define PI 3.141592
#define ERRMSG "Une erreur s'est produite.\n"
#define CARRE(x) ((x) * (x))
Le préprocesseur examine chaque ligne de code à la recherche du nom des macros préalablement
dénies; si l’un des noms est rencontré, son expression est remplacée par sa valeur. Si la macro a
plusieurs paramètres telle que CARRE dans l’exemple ci-dessus, chacun des paramètres est remplacé
littéralement par sa valeur eective. Ainsi, l’écriture suivante
if (CARRE(d)> PI)
printf(ERRMSG);
deviendra après précompilation
if (((d)*(d)) >3.141592)
printf("Une erreur s'est produite.\n");
Les occurences de xont été remplacées littéralement par d.
Une macro a donc une syntaxe similaire à celle d’une fonction bien que leurs traitements par
le compilateur obéissent à des mécanismes sensiblement diérents : les macros résultent de la
précompilation alors que les fonctions sont compilées et donc soumises aux règles du C++ . En
ce sens, les macros sont la source de nombreuses erreurs d’autant plus diciles à repérer qu’on
ne dispose pas de la version étendue du code. À titre d’exemple, l’utilisation de la macro CARRE
suivant les instructions
int i = 3;
int j = CARRE(i++);
provoquera la double incrémentation de la variable ien raison du remplacement de l’instruction
CARRE(i++) par l’expression ((i++) * (i++)) (et non une simple incrémentation comme visiblement
suggéré par le code). Aussi, les macros sont généralement bannies du C++ et remplacées au prot
de déclarations ne faisant pas intervenir le précompilateur.
Ainsi, en C++ , les macros qui dénissent des constantes seront avantageusement remplacées
par des déclarations de la forme
const double Pi = 3.141592;
const std::string ErrMsg = "Une erreur s'est produite";
4
1 / 4 100%
La catégorie de ce document est-elle correcte?
Merci pour votre participation!

Faire une suggestion

Avez-vous trouvé des erreurs dans linterface ou les textes ? Ou savez-vous comment améliorer linterface utilisateur de StudyLib ? Nhésitez pas à envoyer vos suggestions. Cest très important pour nous !