#include
?#
et ##
?#if
?#pragma
?#assert
?Le préprocesseur interprète les directives qui commencent par
#
. 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.
Dans les temps anciens, les ordinateurs n'utilisaient pas ASCII ;
chaque machine avait son propre jeu de caractères. L'ISO a défini
un jeu de caractères supposés présents sur toutes les machines,
c'est l'Invariant Code Set ISO 646-1983. Ce jeu de
caractères ne comporte pas certains caractères intéressants, tels
que les accolades et le backslash. Aussi, le standard
C89 a introduit les trigraphes, séquences de trois caractères
commençant par ??
.
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.
Après le remplacement des trigraphes, le préprocesseur recherche tous les caractères backslash situés en fin de ligne ; chaque occurrence de ce caractère est supprimée, de même que le retour à la ligne qui le suit. Ceci permet d'unifier plusieurs lignes en une seule.
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). C'est une technique qui doit être généralement préférée. On écrira donc pour l'exemple précédent :
printf("Hello" "World !\n");
Un commentaire en C commence par /*
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 vraiment
portable, même si avant C99, des compilateurs acceptaient ce type
de commentaires.
#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.
Il arrive assez souvent qu'un fichier inclus en incluse un autre,
qui lui-même en inclut un autre, etc. On peut arriver à des
boucles, qui peuvent conduire à des redoublements de déclarations
(donc des erreurs, pour 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.
On utilise #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.
On fait ainsi :
#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.
Ce n'est possible qu'en C99. On utilise la construction suivante :
#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 actuellement développés ; mieux vaut l'éviter si le code doit aussi fonctionner avec des compilateurs un peu plus anciens.
Il existe aussi des extensions 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); \ }
Une autre méthode consiste à utiliser le parenthèsage des arguments :
#define PRINTF(s) printf s ... PRINTF (("Vitesse du vent %d m/s", v));
#
et ##
?L'opérateur #
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();
Oui. Mais il est prévu un mécanisme qui empêche les boucles infinies.
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).
On peut redéfinir à l'identique une macro ; ceci est prévu pour les fichiers d'en-tête inclus plusieurs fois. Mais il est en général plus sain de protéger ses fichiers contre l'inclusion multiple (cf. 13.6).
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
sinon0
tous les
identificateurs qui restentL'expression ne doit comporter que des constantes entières (donc,
éventuellement, des constantes caractères), qui sont promues au
type (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
?C'est une indication pour le compilateur. Le préprocesseur envoie cette directive sans la modifier. Le standard C89 ne prévoit aucune directive standard, mais le préprocesseur comme le compilateur sont sensés ignorer les directives inconnues.
Le C99 définit trois #pragma
qui permettent d'ajuster
le comportement du compilateur, quant au traitement des nombres
flottants et complexes.
#assert
?C'est une extension gérée par GNU et (au moins) le
compilateur Sun (Workshop Compiler, pour
Solaris). C'est une sorte d'alternative à
#ifdef
, avec une syntaxe plus agréable.
C'est à éviter, car non standard.
On peut ouvrir un bloc, car tout statement est
remplaçable, en C, par un bloc, mais cela pose des problèmes avec
le ;
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.
Les macros peuvent être dangereuses si l'on ne fait pas attention aux effets de bord. Par exemple si l'on a le code suivant :
#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.
La réponse est oui. Il existe un mouvement qui voudrait faire
disparaître le préprocesseur, en remplaçant les #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é.
Outre le Kernighan &
Ritchie, qui comporte
quelques pages très bien faites sur le sujet, on peut lire la
documentation au format 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 : code.google.com/p/ucpp/).