Il existe deux types de macros : les macros sans paramètre et les macros avec paramètres.
#define nom
reste-de-la-ligne
#define NB_COLONNES 100.
#define NB_LIGNES 24
#define NB_COLONNES 80
#define TAILLE_TAB NB_LIGNES * NB_COLONNES
#define IF if(
#define THEN ){
#define ELSE } else {
#define ELIF } else if (
#define FI ;}
#define BEGIN {
#define END }
#define SWITCH switch(
#define IN ){
#define ENDSW }
#define FOR for(
#define WHILE while(
#define DO ){
#define OD ;}
#define REP do{
#define PER }while(
#undef DONE
#define DONE );
#define LOOP for(;;){
#define POOL }
Et voici un exemple de code :
assign(n,v)
NAMPTR n;
STRING v;
{
IF n->namflg&N_RDONLY
THEN failed(n->namid,wtfailed);
ELSE replace(&n->namval,v);
FI
}
Ce n'est ni du C ni de l'Algol, il y a un consensus dans la communauté C
pour estimer que ce genre de choses est à proscrire.
Ci-dessous, un exemple pour le système UNIX : la compilation du fichier fic.c en définissant la macro sans paramètre de nom NB_LIGNES et de valeur 24 :
cc -c -DNB_LIGNES=24 fic.c
Il y a un certain nombre de macros prédéfinies par le préprocesseur :
| nom | valeur de la macro | forme syntaxique |
__LINE__ |
numéro de la ligne courante du programme source | entier |
__FILE__ |
nom du fichier source en cours de compilation | chaîne |
__DATE__ |
la date de la compilation | chaîne |
__TIME__ |
l'heure de la compilation | chaîne |
__STDC__ |
1 si le compilateur est ISO, 0 sinon | entier |
#define nom
(
liste-de-paramètres-formels
)
reste-de-la-ligne
L'utilité principale des macros avec paramètres est de bénéficier de la clarté
d'expression des fonctions sans en souffrir la lourdeur : le code est inséré
en ligne, donc on économise le code d'entrée et de retour de fonction.
Exemple :
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) < (b) ? (b) : (a))
f()
{
int i,j,k;
i = min(j,k); /* équivalent à : i = j < k ? j : k ; */
i = max(j,k); /* équivalent à : i = j < k ? k : j ; */
}
#define CARRE (a) a * aUne utilisation de
CARRE(2) aura comme expansion (a) a * a(2) !
Attention donc à l'erreur difficile à voir : la présence d'un blanc entre
le nom d'une macro avec paramètres et la parenthèse ouvrante.
struct page {
struct inode *inode;
unsigned long offset;
struct page *next_hash;
atomic_t count;
unsigned flags; /* atomic flags, some possibly updated asynchronously */
... /* d'autres champs */
};
Dans cette structure, le champs flags est un ensemble de bits définis
ci-après :
/* Page flag bit values */ #define PG_locked 0 #define PG_error 1 #define PG_referenced 2 #define PG_uptodate 3 #define PG_free_after 4 #define PG_decr_after 5Puis le programmeur a défini des macros pour tester commodément ces bits à l'aide de la fonction test_bit définie par ailleurs :
/* Make it prettier to test the above... */ #define PageLocked(page) (test_bit(PG_locked, &(page)->flags)) #define PageError(page) (test_bit(PG_error, &(page)->flags)) #define PageReferenced(page) (test_bit(PG_referenced, &(page)->flags)) #define PageUptodate(page) (test_bit(PG_uptodate, &(page)->flags)) #define PageFreeAfter(page) (test_bit(PG_free_after, &(page)->flags)) #define PageDecrAfter(page) (test_bit(PG_decr_after, &(page)->flags))
#define macro(a,b) a [ b
f()
{
int i, t[10];
macro(t,i]) = 1; /* équivalent à t[i] = 1; */
}
Le second paramètre passé à la macro (i]) ne correspond syntaxiquement à
rien, mais le résultat de l'expansion de la macro est correct.
#define CARRE(a) a * aune occurrence de
CARRE(a+b) aura comme expansion a+b * a+b
ce qui est différent du (a+b) * (a+b) qui était désiré.
De la même manière !CARRE(x) aura comme expansion !x * x
ce qui est différent du !(x * x) qui était désiré.
On recommande donc de toujours respecter deux règles dans la définition d'une macro devant être utilisée dans des expressions :
#define CARRE(a) ((a) * (a))
#define CARRE(a) ((a) * (a))l'utilisation de
CARRE(x++) aura comme expansion ((x++) * (x++)),
l'opérateur ++ sera donc appliqué deux fois.
#define F(x) if (!f(x)) { printf("erreur\n"); exit(1); }
La macro pourra s'appeler comme une fonction (avec un ; à la fin)
dans un contexte de liste d'instructions :
{
...
F(i);
...
}
Par contre, dans un contexte d'instruction (et non de liste d'instructions),
il ne faudra pas mettre de ; à la fin :
do F(a) while ( ... );alors qu'il le faudrait si il s'agissait d'une fonction :
do f(a); while ( ... );Mais le pire reste à venir : voyons ce qui se passe si on utilise la macro F dans un if avec else :
if ( ... ) F(i) else ...Il suffit d'imaginer l'expansion de la macro :
if ( ... )
if (!f(x)) { printf("erreur\n"); exit(1); }
else
...
pour comprendre le problème : le else va être raccroché au if
de la macro, ce qui n'est pas ce qu'a voulu le programmeur.
#define F(x) do if (!f(x)) { printf("erreur\n"); exit(1); } while (0)
et tous les problèmes précédents s'évanouissent.