Chapitre 5    Les directives au préprocesseur





Le préprocesseur est un programme exécuté lors de la première phase de la compilation. Il effectue des modifications textuelles sur le fichier source à partir de directives. Les différentes directives au préprocesseur, introduites par le caractère #, ont pour but :

5.1  La directive #include

Elle permet d'incorporer dans le fichier source le texte figurant dans un autre fichier. Ce dernier peut être un fichier en-tête de la librairie standard (stdio.h, math.h,...) ou n'importe quel autre fichier. La directive #include possède deux syntaxes voisines :
#include <nom-de-fichier>
recherche le fichier mentionné dans un ou plusieurs répertoires systèmes définis par l'implémentation (par exemple, /usr/include/) ;
#include "nom-de-fichier"
recherche le fichier dans le répertoire courant (celui où se trouve le fichier source). On peut spécifier d'autres répertoires à l'aide de l'option -I du compilateur.

La première syntaxe est généralement utilisée pour les fichiers en-tête de la librairie standard, tandis que la seconde est plutôt destinée aux fichiers créés par l'utilisateur.



5.2  La directive #define

La directive #define permet de définir :

5.2.1  Définition de constantes symboliques

La directive
#define nom reste-de-la-ligne
demande au préprocesseur de substituer toute occurence de nom par la chaîne de caractères reste-de-la-ligne dans la suite du fichier source. Son utilité principale est de donner un nom parlant à une constante, qui pourra être aisément modifiée. Par exemple :
#define NB_LIGNES 10
#define NB_COLONNES 33
#define TAILLE_MATRICE NB_LIGNES * NB_COLONNES
Il n'y a toutefois aucune contrainte sur la chaîne de caractères reste-de-la-ligne. On peut écrire
#define BEGIN {
#define END }

5.2.2  Définition de macros

Une macro avec paramètres se définit de la manière suivante :
#define nom(liste-de-paramètres) corps-de-la-macro
liste-de-paramètres est une liste d'identificateurs séparés par des virgules. Par exemple, avec la directive
#define MAX(a,b) (a > b ? a : b)
le processeur remplacera dans la suite du code toutes les occurences du type
MAX(x,y)
x et y sont des symboles quelconques par
(x > y ? x : y)
Une macro a donc une syntaxe similaire à celle d'une fonction, mais son emploi permet en général d'obtenir de meilleures performances en temps d'exécution.

La distinction entre une définition de constante symbolique et celle d'une macro avec paramètres se fait sur le caractère qui suit immédiatement le nom de la macro : si ce caractère est une parenthèse ouvrante, c'est une macro avec paramètres, sinon c'est une constante symbolique. Il ne faut donc jamais mettre d'espace entre le nom de la macro et la parenthèse ouvrante. Ainsi, si l'on écrit par erreur
#define CARRE (a) a * a
la chaîne de caractères CARRE(2) sera remplacée par
(a) a * a (2)
Il faut toujours garder à l'esprit que le préprocesseur n'effectue que des remplacements de chaînes de caractères. En particulier, il est conseillé de toujours mettre entre parenthèses le corps de la macro et les paramètres formels qui y sont utilisés. Par exemple, si l'on écrit sans parenthèses :
#define CARRE(a) a * a
le préprocesseur remplacera CARRE(a + b) par a + b * a + b et non par (a + b) * (a + b). De même, !CARRE(x) sera remplacé par ! x * x et non par !(x * x).

Enfin, il faut être attentif aux éventuels effets de bord que peut entraîner l'usage de macros. Par exemple, CARRE(x++) aura pour expansion (x++) * (x++). L'opérateur d'incrémentation sera donc appliqué deux fois au lieu d'une.



5.3  La compilation conditionnelle

La compilation conditionnelle a pour but d'incorporer ou d'exclure des parties du code source dans le texte qui sera généré par le préprocesseur. Elle permet d'adapter le programme au matériel ou à l'environnement sur lequel il s'exécute, ou d'introduire dans le programme des instructions de débogage.

Les directives de compilation conditionnelle se répartissent en deux catégories, suivant le type de condition invoquée :

5.3.1  Condition liée à la valeur d'une expression

Sa syntaxe la plus générale est :
#if condition-1
  partie-du-programme-1
#elif condition-2
  partie-du-programme-2
     ...
#elif condition-n
  partie-du-programme-n
#else
  partie-du-programme-¥
#endif
Le nombre de #elif est quelconque et le #else est facultatif. Chaque condition-i doit être une expression constante.

Une seule partie-du-programme sera compilée : celle qui correspond à la première condition-i non nulle, ou bien la partie-du-programme-¥ si toutes les conditions sont nulles.

Par exemple, on peut écrire
#define PROCESSEUR ALPHA

#if PROCESSEUR == ALPHA
  taille_long = 64;
#elif PROCESSEUR == PC
  taille_long = 32;
#endif

5.3.2  Condition liée à l'existence d'un symbole

Sa syntaxe est
#ifdef symbole
  partie-du-programme-1
#else condition-2
  partie-du-programme-2
#endif
Si symbole est défini au moment où l'on rencontre la directive #ifdef, alors partie-duprogramme-1 sera compilée et partie-du-programme-2 sera ignorée. Dans le cas contraire, c'est partie-du-programme-2 qui sera compilée. La directive #else est évidemment facultative.

Da façon similaire, on peut tester la non-existence d'un symbole par :
#ifndef symbole
  partie-du-programme-1
#else condition-2
  partie-du-programme-2
#endif
Ce type de directive est utile pour rajouter des instructions destinées au débogage du programme :
#define DEBUG
  ....
#ifdef DEBUG
  for (i = 0; i < N; i++)
    printf("%d\n",i);
#endif /* DEBUG */
Il suffit alors de supprimer la directive #define DEBUG pour que les instructions liées au débogage ne soient pas compilées. Cette dernière directive peut être remplacée par l'option de compilation -Dsymbole, qui permet de définir un symbole. On peut remplacer
#define DEBUG
en compilant le programme par
gcc -DDEBUG fichier.c

This document was translated from LATEX by HEVEA.