4. Macros
Une macro-définition | macro-commande | macro-instruction représente une suite d'expressions et d'instructions. On peut distinguer deux groupes de macros, les macros de type constant (sans paramètre) et les macros de type fonction (avec paramètre(s)).
4.1. Macros de type constant
#define constante texte_de_remplacement
#define PI 3.1415926
#define EPSILON 1E-6
#define MAX 100
L'utilisation de macros de type constant | macro-définitions | constantes symboliques permet une meilleure visibilité du code source et une modification simple de la valeur de la constante (Exemple : allocation statique de tableaux de taille MAX. Si on change la taille du tableau, il suffit de changer la valeur de MAX).
Une définition d'une constante symbolique reste valide juqu'à la fin du fichier source sauf si la constante est redéfinie (nouvelle commande #define) ou si elle est invalidée explicitement (commande #undef).
ATTENTION : Les macros sont évaluées (et substituées) lors du preprocessing. Cela veut dire que les constantes n'existent pas en tant qu'objets pour le compilateur (on dit que les variables sont définies dans un espace de nommage différent).
#define MAX 100
int tableau1[MAX];
La défintion de tableau1 est valide : MAX n'est pas une variable mais une constante dont la valeur est connue à la compilation.
int taille = 100;
int tableau2[MAX];
Ce deuxième exemple (tableau2) est clairement faux : la solution est de faire de l'allocation dynamique de mémoire avec des POINTEURS.
4.2. Macros de type fonction
#define macro(liste_de_paramètres) texte_de_remplacement
Les macros peuvent avoir des paramètres dont il n'est pas nécessaire de préciser le type (il faut cependant vérifier la cohérence). Il n'y a pas d'espace entre le nom de la macro et la parenthèse de la liste de paramètres.
Les macros ne sont pas des fonctions, les noms sont substitués avant la compilation : attention donc aux messages d'erreurs donnés par la compilateur et attention aussi aux résultats bizarres dus à des effets de bord :
#define CARRE1(A) (A*A)
#define CARRE2(A) ((A)*(A))
#define MAX1(A,B) A>B ? A : B
#define MAX2(A,B) (((A)>(B))?(A):(B))
int i = 2; CARRE2(i+1)
renvoie 9 mais CARRE1(i+1)
renvoie 5 !
int i=2, j=3; MAX1(i,j)
et MAX2(i,j)
renvoient
bien le bon résultat (3) !
int i=2, j=3; 6+MAX1(i,j)
vaut 2 et non pas 3
int i=2, j=3;
Calculer MAX2(i++,j++)
donne 4
et les valeurs des variables i et j sont modifiées
... i vaut 3 et j vaut 5.
Il faut donc faire attention lorsque l'on écrit des macros mais les macros peuvent se révéler très utiles dans de nombreux cas... Le parenthèsage systématique des arguments de la macro limite les risques.
#define MALLOC(TYPE,NOMBRE) ((TYPE *)malloc(sizeof(TYPE)*NOMBRE))
int * pi, * pj;
float * pf;
pi = MALLOC(int, 10); /* crée un tableau de 10 entiers */
pf = MALLOC(float, 100); /* crée un tableau de 10 réels */
pj = MALLOC(float, 100); /* n'est pas correct */
/* (les types ne sont pas respectés) */
Considérons la fonction suivante :
void allouer(void ** pointeur, int nb, int taille) {
if ((*pointeur=malloc(nb*taille)) == NULL)
erreur();
}
appelée comme ceci, pour créer un tableau de 10 entiers :
int *pi;
allouer(&pi, 10, sizeof(int));
La macro suivante simplifie la tâche et évite de passer
par un "pointeur de pointeur" :
#define ALLOUER(PTR,NB,TYPE) \
if ((PTR=(TYPE *)malloc(NB*sizeof(TYPE)))==NULL) erreur()
Les macros sont en général plus rapides que les fonctions
(ce n'est que du remplacement de caractères : il n'y a pas de mécanisme
d'appel de fonction, ce qui veut dire pas de saut en mémoire ni
de paramètres empilés). Toutefois, le gain en vitesse potentiel
est perdu si les arguments sont long à évaluer. MAX(f(x),g(x))
peut être très long (chaque fonction est évaluée
deux fois et non une seule fois pour une fonction).
4.3. Opérateurs spéciaux
(1) L'opérateur # appliqué à un argument l'entoure de guillemets.
#define DUMP1(X) printf("%s == %d", #X, X)
ou
#define DUMP2(X) printf(#X " == %d", X)
/* Remarque : "x""y" donne "xy"
int a = 1;
affichera
DUMP1(a);a==1
à l'éxécution... Pratique pour le débogage, non ?
(2) L'opérateur ## permet de concaténer plusieurs paramètres.
#define COLLER(A, B) A##B
/* Remarque : "x""y" donne "xy"
COLLER(A, 1)
donnera A1
.
Attention, COLLER(COLLER(A,1),2)
ne donne pas A12
mais COLLER(A,1)2
car l'opérateur ## empêche un développement
supplémentaire.
Pour résoudre ce problème, il faut définir par exemple :
#define XCOLLER(A,B) COLLER(A,B)
et alors XCOLLER(XCOLLER(A,1),2)
renvoie bien A12
.
4.4. Résumé
Avantages :
- Une macro simplifie l'écriture du code
- Une macro est en souvent plus rapide à l'exécution qu'une fonction (le temps est gagné sur l'appel de fonction : il n'y a pas d'empilage de paramètres et pas de mécanisme d'appel...) au détriment de la taille du code.
Désavantages :
- L'utilisation de macros peut augmenter sensiblement la taille du code source (le texte de la macro est copié à chaque "appel" de macro)
- Il faut faire attention aux effets de bord de la macro
- Les arguments sont évalués autant de fois qu'ils sont présents (ou presque). Si leur évaluation est longue, le temps d'exécution de la macro peut devenir très long.
Guide de style :
Pour différencier facilement les macros des fonctions ou des variables lors de la lecture du source et de leur utilisation, on note les macros en MAJUSCULES.