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 :
recherche le fichier mentionné dans un ou plusieurs répertoires
systèmes définis par l'implémentation (par exemple, /usr/include/) ;
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.
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
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.
Les directives de compilation conditionnelle se répartissent en deux catégories, suivant le type de condition invoquée :
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
Da façon similaire, on peut tester la non-existence d'un symbole par :
- l'incorporation de fichiers source (#include),
- la définition de constantes symboliques et de macros (#define),
- la compilation conditionnelle (#if, #ifdef,...).
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>
#include "nom-de-fichier"
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 :- des constantes symboliques,
- des macros avec paramètres.
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_COLONNESIl 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
où
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)où 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 * ala 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 * ale 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 :
- la valeur d'une expression
- l'existence ou l'inexistence de symboles.
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 #endifSi 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 #endifCe 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 DEBUGen compilant le programme par
gcc -DDEBUG fichier.c
0 comments:
Post a Comment