#include
?#
et ##
?#if
?#pragma
?#assert
?#
. Principalement, ces directives permettent d'inclure
d'autres fichiers (via #include
) et de définir des
macros (via #define
) qui sont remplacées lors de la
compilation.
Chaque directive de compilation commence par un #
situé
en début de ligne (mais éventuellement précédé par des espaces,
des tabulations ou des commentaires) et se termine en fin de
ligne.
Le préprocesseur est également responsable de la reconnaissance des trigraphes, des backslashs terminaux, et de l'ablation des commentaires.
??
.
Il existe neuf séquences remplacées par le préprocesseur.
Ce remplacement a lieu avant toute autre opération, et agit
également dans les commentaires, les chaînes constantes, etc.
Les trigraphes sont, de fait, rarement utilisés. On les voit apparaître occasionnellement et par erreur, quand on écrit ça :
printf("Kikoo ??!\n");
Le trigraphe est remplacé par un pipe, donc ce code affiche ceci :
Kikoo |
De toutes façons, le redoublement du point d'interrogation est de mauvais goût.
Les compilateurs modernes soient ne reconnaissent plus les trigraphes, soit émettent des avertissements quand ils les rencontrent.
Ce comportement est pratique pour écrire des macros ou des chaînes de caractères sur plusieurs lignes :
printf("Hello\
World !\n");
Pour les chaînes de caractères, on peut aussi écrire plusieurs chaînes côte à côte, et le compilateur les unifiera (mais pas le préprocesseur : pour lui, ce seront deux chaînes à la suite l'une de l'autre).
/*
et se termine par
*/
, éventuellement plusieurs lignes plus loin. Les
commentaires ne s'imbriquent pas.
On peut aussi utiliser la compilation conditionnelle, comme ceci :
#if 0
/* ceci est ignore */
#endif /* 0 */
Dans ce cas, il faut que ce qui est ignoré soit une suite de
tokens valide (le préprocesseur va quand même les
regarder, afin de trouver le #endif
). Ceci veut dire
qu'il ne faut pas de chaîne non terminées. Ce genre de
commentaire n'est pas adapté à du texte, à cause des apostrophes.
La nouvelle norme du C (C99) permet d'utiliser les commentaires
du C++ : ils commencent par //
et se terminent en fin de
ligne. Ce type de commentaire n'est pas encore supporté partout,
donc mieux vaut ne pas s'en servir si on veut faire du code
portable.
#include
?#include
comporte trois formes principales :
#include <fichier>
#include "fichier"
#include tokens
La première forme recherche le fichier indiqué dans les
répertoires système ; on peut les ajuster soit via des menus (dans
le cas des compilateurs avec une interface graphique), soit en
ligne de commande. Sur un système Unix, le répertoire
système classique est /usr/include/
. Une fois le fichier
trouvé, tout se passe comme si son contenu était tel quel dans le
code source, là où se trouve le #include
.
La deuxième forme recherche le fichier dans le répertoire courant. Si le fichier ne s'y trouve pas, il est ensuite cherché dans les répertoires systèmes, comme dans la première forme.
La troisième forme, où ce qui suit le #include
ne
correspond pas à une des deux formes précédentes, commence par
effectuer tous les remplacements de macros dans la suite de
tokens, et le résultat doit être d'une des deux formes
précédentes.
Si le fichier n'est pas trouvé, c'est une erreur, et la
compilation s'arrête. On notera que si on se sert d'habitude de
#include
pour inclure des fichiers d'en-tête (tels que
stdio.h
), ce n'est pas une obligation.
typedef
par exemple), voire des
boucles infinies (le compilateur finissant par planter).
Pour cela, le moyen le plus simple est de « protéger » chaque fichier par une construction de ce genre :
#ifndef FOO_H_
#define FOO_H_
/* ici, contenu du fichier */
#endif /* FOO_H_ */
Ainsi, même si le fichier est inclus plusieurs fois, son contenu
ne sera actif qu'une fois. Certains préprocesseurs iront même
jusqu'à reconnaître ces structures, et ne pas lire le fichier
si la macro de protection (ici FOO_H_
) est encore
définie.
Il y a eut divers autres moyens proposés, tels que #import
ou #pragma once
, mais ils ne sont pas standards, et encore
moins répandus.
#define
. La forme la plus simple est la
suivante :
#define FOO 3 + 5
Après cette déclaration, toute occurrence de l'identificateur
FOO
est remplacée par son contenu (ici 3 + 5
).
Ce remplacement est syntaxique et n'a pas lieu dans les chaînes
de caractères, ou dans les commentaires (qui n'existent déjà plus,
de toutes façons, à cette étape).
On peut définir un contenu vide. La macro sera remplacée, en cas d'utilisation, par rien. Le contenu de la macro est une suite de tokens du C, qui n'a pas à vouloir dire quelque chose. On peut définir ceci, c'est valide :
#define FOO (({/coucou+}[\}+ "zap" 123
La tradition est d'utiliser les identificateurs en majuscules pour les macros ; rien n'oblige cependant à appliquer cet usage. Les règles pour les identificateurs de macros sont les mêmes que pour celles du langage.
#define FOO(x, y) ((x) + (x) * (y))
Ceci définit une macro qui attend deux arguments ; notez qu'il
n'y a pas d'espace entre le nom de la macro (FOO
) et la
parenthèse ouvrante. Toute invocation de la macro par la suite
est remplacée par son contenu, les arguments l'étant aussi.
Ainsi, ceci :
FOO(bla, "coucou")
devient ceci :
((bla) + (bla) * ("coucou"))
(ce qui ne veut pas dire grand'chose en C, mais le préprocesseur n'en a cure). Le premier argument est remplacé deux fois, donc, s'il a des effets de bord (appel d'une fonction, par exemple), ces effets seront présents deux fois.
Si la macro est invoquée sans arguments, elle n'est pas remplacée. Cela permet de définir une macro sensée remplacer une fonction, mais en conservant la possibilité d'obtenir un pointeur sur la fonction. Ainsi :
int min(int x, int y) { return x < y ? x : y; }
#define min(x, y) ((x) < (y) ? (x) : (y))
min(3, 4); /* invocation de la macro */
(min)(3, 4); /* invocation de la fonction, via le pointeur */
C'est une erreur d'invoquer une macro à argument avec un nombre incorrect d'arguments. En C99, on peut utiliser des arguments vides ; en C89, c'est flou et mieux vaut éviter.
#define error(l, ...) { \
fprintf(stderr, "line %d: ", l); \
fprintf(stderr, __VA_ARGS__); \
}
Ceci définit une macro, qui attend au moins un argument ; tous
les arguments supplémentaires sont concaténés, avec leurs virgules
de séparation, et on peut les obtenir en utilisant
__VA_ARGS__
. Ainsi, ceci :
error(5, "boo: '%s'\n", bla)
sera remplacé par ceci :
{ fprintf(stderr, "line %d: ", 5); \
fprintf(stderr, "boo: '%s'\n", bla); }
Ce mécanisme est supporté par les dernières versions de la plupart des compilateurs C activement développés ; mieux vaut l'éviter si le code doit aussi fonctionner avec des compilateurs un peu plus anciens.
Il existe aussi des extentions sur certains compilateurs. Par exemple, sous GCC, le code suivant est équivalent à l'exemple précédent :
#define error(l, format...) { \
fprintf(stderr, "line %d: ", l); \
fprintf(stderr, format); \
}
#
et ##
?#
permet de transformer un argument d'une
macro en une chaîne de caractères. On fait ainsi :
#define BLA(x) printf("l'expression '%s' retourne %d\n", #x, x);
BLA(5 * x + y);
ce qui donne le résultat suivant :
printf("l'expression '%s' retourne %d\n", "5 * x + y", 5 * x + y);
Les éventuelles chaînes de caractères et backslashs dans l'argument sont protégés par des backslashes, afin de constituer une chaîne valide.
L'opérateur ##
effectue la concaténation de deux
tokens. Si le résultat n'est pas un
token valide, alors c'est une erreur ; mais certains
préprocesseurs sont peu stricts et se contentent de re-séparer
les tokens.
On l'utilise ainsi :
#define FOO(x, y) x ## y
FOO(bar, qux)();
qui donne ceci :
barqux();
Tout d'abord, les invocations de macros ne sont constatées que lors de l'utilisation de la macro, pas lors de sa définition. Si on fait ceci :
#define FOO BAR
#define BAR 100
alors on obtient bien 100
, pas BAR
.
Si la macro possède des arguments, chaque fois que cet argument
est utilisé (sans être précédé d'un #
ou précédé ou suivi
d'un ##
), il est d'abord examiné par le préprocesseur,
qui, s'il y reconnaît une macro, la remplace. Une fois les
arguments traités, le préprocesseur les implante à leur place
dans la suite de tokens générés par la macro, et gère
les opérateurs #
et ##
.
À la suite de cette opération, le résultat est de nouveau examiné pour rechercher d'autres remplacements de macros ; mais si une macro est trouvée, alors qu'on est dans le remplacement de ladite, cette macro n'est pas remplacée. Ceci évite les boucles infinies.
Je sais, c'est compliqué. Quelques exemples :
#define FOO coucou BAR
#define BAR zoinx FOO
FOO
FOO
est remplacée par coucou BAR
, et le
BAR
résultant est remplacé par zoinx FOO
. Ce
FOO
n'est pas remplacé, parce qu'on est dans le
remplacement de FOO
. Donc, on obtient coucou zoinx
FOO
.
Un autre exemple, plus tordu :
#define FOO(x) x(5)
FOO(FOO);
La macro FOO
est invoquée ; elle attend un argument, qui
est FOO
. Cet argument est d'abord examiné ; il y a
FOO
dedans, mais non suivi d'une parenthèse ouvrante
(l'argument est examiné tout seul, indépendamment de ce qui le
suit lors de son usage), donc le remplacement n'a pas
lieu. Ensuite, l'argument est mis en place, et on obtient
FOO(5)
. Ce résultat est réexaminé ; cette fois,
FOO
est bien invoquée avec un argument, mais on est dans
le deuxième remplacement, à l'intérieur de la macro FOO
,
donc on ne remplace pas.
Le résultat est donc : FOO(5);
Si vous voulez utiliser ce mécanisme, allez lire une douzaine de fois la documentation du GNU cpp, et surtout le paragraphe 12 de l'annexe A du Kernighan & Ritchie (2ème édition).
Redéfinir une macro avec un contenu ou des arguments différents,
est une erreur. Certains préprocesseurs laxistes se contentent
d'un avertissement. La bonne façon est d'abord d'indéfinir la
macro via un #undef
. Indéfinir une macro qui n'existe
déjà pas, n'est pas une erreur.
#if
?#if
permet la compilation conditionnelle. L'expression
qui suit le #if
est évaluée à la compilation, et, suivant
son résultat, le code qui suit le #if
jusqu'au prochain
#endif
, #elif
ou #else
est
évalué, ou pas.
Quand le code n'est pas évalué, les directives de préprocesseur
ne le sont pas non plus ; mais les #if
et similaires sont
néanmoins comptés, afin de trouver la fin de la zone non compilée.
Lorsque le préprocesseur rencontre un #if
, il :
#if
defined MACRO
et
defined(MACRO)
par la constante 1
si
la macro nommée est définie, 0
sinon
0
tous les
identificateurs qui restent
(unsigned) long
(en C89) ou (u)intmax_t
(en
C99). Les flottants, les pointeurs, l'accès à un tableau, et
surtout l'opérateur sizeof
ne sont pas utilisables par le
préprocesseur.
Il n'est pas possible de faire agir un #if
suivant
sizeof(long)
, pour reprendre un desiderata
fréquent. Par ailleurs, les constantes de type caractère n'ont pas
forcément la même valeur pour le préprocesseur et pour le
compilateur.
#pragma
?
Le C99 définit trois #pragma
qui permettent d'ajuster
le comportement du compilateur, quant au traitement des nombres
flottants et complexes.
#assert
?#ifdef
, avec une syntaxe plus agréable.
C'est à éviter, car non standard.
;
terminal du statement. La manière
recommandée est la suivante :
#define foo(x) do { f(x); printf("coucou\n"); } while (0)
On peut ainsi l'utiliser comme ceci :
if (bar) foo(1); else foo(2);
Si on avait défini foo
sans le do
et le
while (0)
, le code ci-dessus aurait provoqué une erreur
de compilation, car le else
serait séparé du if
par deux statements : le bloc et le statement vide, terminé par le
point-virgule.
#define MAX 3 + 5
int i = MAX;
La variable i
vaudra bien 8, mais si on utilise la
macro MAX
ainsi :
int i = MAX * 2;
La variable i
ne vaudra pas 16, mais 13. Pour éviter
ce genre de comportement, il faut écrire la macro
ainsi :
#define MAX (3 + 5)
Dans certains cas, une macro représente une expression C complète. Il est alors plus cohérent de placer des parenthèses vides pour simuler une fonction. Et dans ce cas il ne faut pas la terminer par un point virgule, et
#define PRTDEBUG() (void)printf("Coucou\n")
ainsi, on pourra utiliser la macro par :
if (i == 10) {
PRTDEBUG();
}
else {
i++;
}
Quand une macro a des arguments il faut faire attention à la façon de les utiliser. Ainsi la macro :
#define CALCUL(x, y) (x + y * 2)
a des effets de bord suivant la façon de l'utiliser :
int i = CALCUL(3, 5);
donnera bien un résultat de 13, alors que le même résultat serait attendu avec :
int i = CALCUL(3, 2 + 3);
qui donne 11. Pour éviter cela, il suffit de placer des parenthèses sur les arguments de la macro:
#define CALCUL(x, y) ((x) + (y) * 2)
Un effet de bord qui ne peut être contourné survient quand la macro utilise plusieurs fois une variable :
#define MAX(x, y) ((x) > (y) ? (x) : (y))
Si on utilise la macro comme cela :
i = MAX(j, k);
On obtiendra un résultat correct, alors qu'avec :
i = MAX(j++, k++);
une des variables j
ou k
sera incrémentée 2
fois. Pour éviter ce genre de comportement, il faut remplacer la
macro par une fonction, de préférence inline (C99) :
inline int max(int x, int y)
{
return x > y ? x : y;
}
En règle générale, quand on utilise une fonction avec un nom en majuscule (comme MAX), on s'attend à ce que ce soit en fait une macro, avec les effets de bord qui en découlent. Alors que si le nom est en minuscule, il s'agit sûrement d'une véritable fonction. Cette règle n'est hélas pas générale et donc il convient de vérifier le véritable type d'une fonction si l'on ne veut pas être surpris lors de son utilisation.
#define
par des variables constantes, et avec un quelconque artifice
syntaxique pour importer les déclarations d'un fichier d'entête.
Il s'avère qu'on a vraiment besoin d'un mécanisme polymorphe (comme une fonction qui accepterait plusieurs types différents), et que seules les macros apportent ce mécanisme en C. Les anti-préprocesseurs acharnés parlent d'adopter le mécanisme des templates du C++, mais ça ne risque pas d'arriver de sitôt.
Dans la vie de tous les jours, l'utilisation du préprocesseur,
avec quelques #include
et des #define
sans
surprise, ne pose pas de problème particulier, ni de maintenance,
ni de portabilité.
info
du GNU cpp ;
elle est assez partiale dans certains cas (condamnation explicite
des trigraphes, par exemple) mais assez riche en enseignements.
Pour le reste, chaque compilateur C vient avec un préprocesseur, et il existe quelques préprocesseurs indépendants (un écrit par Dennis Ritchie en personne, qu'on voit inclus dans lcc, et aussi ucpp, une oeuvre à moi : www.di.ens.fr/~pornin/ucpp/ ).
faq-fclc 5/3/2002 (8h 59:05)