Developpez.com - C
X

Choisissez d'abord la catégorieensuite la rubrique :

next up previous contents index
Next: Compilation conditionnelle Up: Le préprocesseur Previous: Le préprocesseur

Sous-sections

     
Traitement de macros

Il existe deux types de macros : les macros sans paramètre et les macros avec paramètres.

Les macros sans paramètres

  Les macros sans paramètre ont été introduites au paragraphe 1.10.1. Rappelons que lorsque le préprocesseur lit une ligne du type :

#define nom reste-de-la-ligne

il remplace dans toute la suite du source, toute nouvelle occurrence de nom par reste-de-la-ligne. Il n'y a aucune contrainte quand à ce qui peut se trouver dans reste-de-la-ligne, mais l'utilité principale des macros sans paramètre est de donner un nom parlant à une constante. Les avantages à toujours donner un nom aux constantes sont les suivants :

1.
un nom bien choisi permet d'expliciter la sémantique de la constante. Exemple : #define NB_COLONNES 100.
2.
la constante chiffrée se trouve à un seul endroit, ce qui facilite la modification du programme quand on veut changer la valeur de la constante (cas de la taille d'un tableau, par exemple).
3.
on peut expliciter les relations entre constantes. Exemple :
#define NB_LIGNES 24
#define NB_COLONNES 80
#define TAILLE_TAB NB_LIGNES * NB_COLONNES

Exemple de mauvaise utilisation

  Du fait de l'absence de contrainte sur reste-de-la-ligne, on peut faire des choses très déraisonnables avec les macros. Un exemple célèbre est le source du shell écrit par Steve Bourne pour le système UNIX. Bourne avait utilisé les facilités de macros pour programmer dans un dialecte de Algol 68. Voici un extrait de ses définitions :
#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.

   
Définition de macro à l'invocation du compilateur

Certains compilateurs permettent de définir des macros sans paramètres à l'invocation du compilateur. Il est alors possible d'écrire un programme utilisant une macro qui n'est nulle part définie dans le source. La définition se fera à l'invocation du compilateur. Ceci est très pratique pour que certaines constantes critiques d'un programme aient une valeur qui soit attribuée à l'extérieur du programme, par une phase de configuration par exemple.

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

Macros prédéfinies

           

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

Les macros avec paramètres

      Une macro avec paramètres se définit de la manière suivante :
#define nom ( liste-de-paramètres-formels ) reste-de-la-ligne

La liste-de-paramètres-formels est une liste d'identificateurs séparés par des virgules. Le reste-de-la-ligne est appelé << corps de la macro >>. Toute occurrence ultérieure de nom sera un appel de la macro et devra avoir la forme :
nom ( liste-de-paramètres-effectifs )
Dans la liste-de-paramètres-effectifs, les paramètres sont séparés par des virgules et chaque paramètre est une suite quelconque d'unités lexicales. Le préprocesseur remplace l'ensemble nom de la macro et liste de paramètres effectifs parenthésés, par reste-de-la-ligne dans lequel chaque paramètre formel est remplacé par le paramètre effectif correspondant. Cette opération de remplacement de texte porte le nom d'expansion de la macro.

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 ;              */
}

Attention

La distinction entre macro avec et sans paramètre 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 macro sans paramètre. En particulier, si après le nom de la macro il y a un blanc avant la parenthèse ouvrante, ça sera une macro sans paramètre. Exemple :
#define CARRE (a) a * a
Une 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.

Exemple

  Cet exemple est tiré du source de LINUX. Il s'agit d'un fragment de gestion de la mémoire virtuelle, une structure page a été définie :
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            5
Puis 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))

Exemple de mauvaise utilisation

Dans une invocation de macro, chaque paramètre effectif peut être une suite quelconque d'unités lexicales, mais après expansion, le texte obtenu doit être un fragment valide de langage C. Voici un exemple des horreurs que l'on peut écrire en utilisant les macros :
#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.

Les pièges des macros

Par le fait que le traitement des macros consiste à faire de la substitution de texte, l'écriture de macros recèle de nombreux pièges.

Pièges des priorités d'opérateurs

Supposons que l'on écrive :
#define CARRE(a) a * a
une 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 :

1.
parenthéser les occurrences des paramètres formels ;
2.
parenthéser le corps complet de la macro.
Une définition de CARRE respectant ces règles est :
#define CARRE(a) ((a) * (a))

Pièges des effets de bord

L'utilisation d'effet de bord sur les paramètres effectifs d'une macro peut avoir des effets complètement inattendus. Après la définition :
#define CARRE(a) ((a) * (a))
l'utilisation de CARRE(x++) aura comme expansion ((x++) * (x++)), l'opérateur ++ sera donc appliqué deux fois.

Macros générant des instructions

Tous les exemples donnés jusqu'ici sont des exemples de macros générant des expressions. Les macros peuvent aussi générer des instructions et là aussi il y a des pièges à éviter. Supposons qu'ayant à écrire un grand nombre de fois un appel de fonction avec test d'erreur, on définisse la macro suivante :
#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.

Recommandation

  Pour toute macro générant des instructions, on recommande d'englober les instructions générées dans la partie instruction d'un do ... while (0). Notre exemple s'écrit ainsi :
#define F(x) do if (!f(x)) { printf("erreur\n"); exit(1); } while (0)
et tous les problèmes précédents s'évanouissent.


next up previous contents index
Next: Compilation conditionnelle Up: Le préprocesseur Previous: Le préprocesseur
Bernard Cassagne
1998-12-09
Contacter le responsable de la rubrique C