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.