• Aucun résultat trouvé

Chapitre 4. Les pointeurs et références

5.2. Les directives du préprocesseur

Unedirectiveest une commande pour le préprocesseur. Toutes les directives du préprocesseur com-mencent :

en début de ligne ;

par un signe dièse (#).

Le préprocesseur dispose de directives permettant d’inclure des fichiers, de définir des constantes de compilation, de supprimer conditionnellement des blocs de texte, et de générer des erreurs ou de modifier l’environnement de compilation.

5.2.1. Inclusion de fichier

L’inclusion de fichier permet de factoriser du texte commun à plusieurs autres fichiers (par exemple des déclarations de type, de constante, de fonction, etc.). Les déclarations des fonctions et des constantes sont ainsi généralement factorisées dans un fichier d’en-tête portant l’extension.hpour

« header », fichier d’en-tête de programme).

Par exemple, nous avons déjà vu les fichiers d’en-têtestdlib.h,stdio.h etstring.hdans les chapitres précédents. Ce sont vraissemblablement les fichiers d’en-tête de la bibliothèque C les plus couramment utilisés. Si vous ouvrez le fichierstdio.h, vous y verrez la déclaration de toutes les fonctions et de tous les types de la bibliothèque d’entrée - sortie standard (notez qu’elles sont peut-être définies dans d’autres fichiers d’en-tête, eux-mêmes inclus par ce fichier). De même, vous trouverez sans doute les déclarations des fonctionsmallocetfreedans le fichier d’en-têtestdlib.h. La syntaxe générale pour la directive d’inclusion de fichier est la suivante :

#include "fichier"

ou :

#include <fichier>

Chapitre 5. Le préprocesseur C

fichierest le nom du fichier à inclure. Lorsque son nom est entre guillemets, le fichier spécifié est recherché dans le répertoire courant (normalement le répertoire du programme). S’il est encadré de crochets, il est recherché d’abord dans les répertoires spécifiés en ligne de commande avec l’option -I, puis dans les répertoires du chemin de recherche des en-têtes du système (ces règles ne sont pas fixes, elles ne sont pas normalisées).

Le fichier inclus est traité lui aussi par le préprocesseur.

5.2.2. Constantes de compilation et remplacement de texte

Le préprocesseur permet de définir des identificateurs qui, utilisés dans le programme, seront rempla-cés textuellement par leur valeur. La définition de ces identificateurs suit la syntaxe suivante :

#define identificateur texte

oùidentificateurest l’identificateur qui sera utilisé dans la suite du programme, ettextesera le texte de remplacement que le préprocesseur utilisera. Le texte de remplacement est facultatif (dans ce cas, c’est le texte vide). À chaque fois que l’identificateuridentificateursera rencontré par le préprocesseur, il sera remplacé par le textetextedans toute la suite du programme.

Cette commande est couramment utilisée pour définir desconstantes de compilation, c’est-à-dire des constantes qui décrivent les paramètres de la plateforme pour laquelle le programme est compilé. Ces constantes permettent de réaliser descompilations conditionnelles, c’est-à-dire de modifier le com-portement du programme en fonction de paramètres définis lors de sa compilation. Elle est également utilisée pour remplacer des identificateurs du programme par d’autres identificateurs, par exemple afin de tester plusieurs versions d’une même fonction sans modifier tout le programme.

Exemple 5-1. Définition de constantes de compilation

#define UNIX_SOURCE

#define POSIX_VERSION 1001

Dans cet exemple, l’identificateurUNIX_SOURCEsera défini dans toute la suite du programme, et la constante de compilationPOSIX_VERSIONsera remplacée par1001partout où elle apparaîtra.

Note :On fera une distinction bien nette entre les constantes de compilation définies avec la directive #definedu préprocesseur et les constantes définies avec le mot clé const. En ef-fet, les constantes littérales ne réservent pas de mémoire. Ce sont des valeurs immédiates, définies par le compilateur. En revanche, les variables de classe de stockage constpeuvent malgré tout avoir une place mémoire réservée. Ce peut par exemple être le cas si l’on manipule leur adresse ou s’il ne s’agit pas de vraies constantes, par exemple si elles peuvent être modi-fiées par l’environnement (dans ce cas, elles doivent être déclarées avec la classe de stockage volatile). Ce sont donc plus des variables accessibles en lecture seule que des constantes. On ne pourra jamais supposer qu’une variable ne change pas de valeur sous prétexte qu’elle a la classe de stockageconst, alors qu’évidemment, une constante littérale déclarée avec la directive

#definedu préprocesseur conservera toujours sa valeur (pourvu qu’on ne la redéfinisse pas).

Par ailleurs, les constantes littérales n’ont pas de type, ce qui peut être très gênant et source d’erreur. On réservera donc leur emploi uniquement pour les constantes de compilation, et on préférera le mot cléconstpour toutes les autres constantes du programme.

Le préprocesseur définit un certain nombre de constantes de compilation automatiquement. Ce sont les suivantes :

Chapitre 5. Le préprocesseur C

__LINE__: donne le numéro de la ligne courante ;

__FILE__: donne le nom du fichier courant ;

__DATE__: renvoie la date du traitement du fichier par le préprocesseur ;

__TIME__: renvoie l’heure du traitement du fichier par le préprocesseur ;

__cplusplus : définie uniquement dans le cas d’une compilation C++. Sa valeur doit être 199711L pour les compilateurs compatibles avec le projet de norme du 2 décembre 1996. En pratique, sa valeur est dépendante de l’implémentation utilisée, mais on pourra utiliser cette chaîne de remplacement pour distinguer les parties de code écrites en C++ de celles écrites en C.

Note : Si __FILE__,__DATE__, __TIME__ et__cplusplus sont bien des constantes pour un fichier donné, ce n’est pas le cas de__LINE__. En effet, cette dernière « constante » change bien évidemment de valeur à chaque ligne. On peut considérer qu’elle est redéfinie automatiquement par le préprocesseur à chaque début de ligne.

5.2.3. Compilation conditionnelle

La définition des identificateurs et des constantes de compilation est très utilisée pour effectuer ce que l’on appelle lacompilation conditionnelle. La compilation conditionnelle consiste à remplacer certaines portions de code source par d’autres, en fonction de la présence ou de la valeur de constantes de compilation. Cela est réalisable à l’aide des directives de compilation conditionnelle, dont la plus courante est sans doute#ifdef:

#ifdef identificateur ..

.

#endif

Dans l’exemple précédent, le texte compris entre le#ifdef(c’est-à-dire « if defined ») et le#endif est laissé tel quel si l’identificateuridentificateurest connu du préprocesseur. Sinon, il est sup-primé. L’identificateur peut être déclaré en utilisant simplement la commande#definevue précé-demment.

Il existe d’autres directives de compilation conditionnelle :

#ifndef (if not defined ...)

#elif (sinon, si ... )

#if (si ... )

La directive#ifattend en paramètre une expression constante. Le texte qui la suit est inclus dans le fichier si et seulement si cette expression est non nulle. Par exemple :

#if (__cplusplus==199711L) ..

.

#endif

permet d’inclure un morceau de code C++ strictement conforme à la norme décrite dans le projet de norme du 2 décembre 1996.

Chapitre 5. Le préprocesseur C

Une autre application courante des directives de compilation est la protection des fichiers d’en-tête contre les inclusions multiples :

#ifndef DejaLa

#define DejaLa

Texte à n’inclure qu’une seule fois au plus.

#endif

Cela permet d’éviter que le texte soit inclus plusieurs fois, à la suite de plusieurs appels de#include. En effet, au premier appel,DejaLan’est pas connu du préprocesseur. Il est donc déclaré et le texte est inclus. Lors de tout autre appel ultérieur, DejaLaexiste, et le texte n’est pas inclus. Ce genre d’écriture se rencontre dans les fichiers d’en-tête, pour lesquels en général on ne veut pas qu’une inclusion multiple ait lieu.

5.2.4. Autres directives

Le préprocesseur est capable d’effectuer d’autres actions que l’inclusion et la suppression de texte.

Les directives qui permettent d’effectuer ces actions sont indiquées ci-dessous :

#: ne fait rien (directive nulle) ;

#error message: permet de stopper la compilation en affichant le message d’erreur donné en paramètre ;

#line numéro [fichier]: permet de changer le numéro de ligne courant et le nom du fichier courant lors de la compilation ;

#pragma texte: permet de donner des ordres spécifiques à une l’implémentation du compilateur tout en conservant la portabilité du programme. Toute implémentation qui ne reconnaît pas un ordre donné dans une directive#pragmadoit l’ignorer pour éviter des messages d’erreurs. Le format des ordres que l’on peut spécifier à l’aide de la directive#pragman’est pas normalisé et dépend de chaque compilateur.