Exood4 Studio - Video Game Development, Toulouse (France)
Exood4 Studios Exood4 Tutorials

   
L a n g a g e   C

L e s   m a c r o s






Définition et invocation


 Une macro n'est rien d'autre qu'un symbole dont la valeur est paramétrable :

#define SIGNE(x) x < 0 ? -1 : 1

 L'invocation de la macro est similaire à l'appel d'une fonction :

int s, z = 5;
s = SIGNE(z); /* sera remplacé lors de la compilation par : s = z < 0 ? -1 : 1; */

 On gagne ici le temps et le code qui auraient été nécessaires à l'appel d'une fonction. Par contre, la macro est développée à chaque invocation, ce qui augmente la taille de l'exécutable en fonction du nombre d'utilisations.

Attention à la priorité des opérateurs !!

#define MULTIPLIE(x, y) x * y

MULTIPLIE(3,5); /* sera remplacé par 3 * 5, le résultat sera 15 */
MULTIPLIE(1+2,3+2); /* sera remplacé par 1+2 * 3+2, le résultat sera 9 (1+6+2) !!! */

 Par sécurité l'écriture des macros se fera en entourant les paramètres par des parenthèses dans l'expression :

#define MULTIPLIE(x, y) ((x) * (y))

MULTIPLIE(3,5); /* sera remplacé par ((3) * (5)), le résultat sera 15 */
MULTIPLIE(1+2,3+2); /* sera remplacé par ((1+2) * (3+2)), le résultat sera 15 */


Macros et fonctions


 Les macros offrent une certaine souplesse par rapport aux fonctions. Par exemple, la macro SIGNE définie ci-dessus fonctionne pour tout type d'argument accepté comme opérande de <.

 Les mots clés peuvent être manipulés comme toute autre chaîne de caractères :

#define MALLOC(NB, TYPE) (TYPE *) malloc ((NB)*sizeof(TYPE))

ptr = MALLOC(100, int);

 Par exemple pour contrôler que la mémoire est disponible lors d'une allocation, il est plus souple de se servir d'une macro que d'une fonction qui nécessite de passer l'adresse du pointeur :

/* Pour la version macro */
#define ALLOUER(PTR, NB, TYPE) \
 if ( (PTR = malloc ((NB) * sizeof(TYPE))) == NULL) erreur ()

int *ptr;
ALLOUER (ptr,100,int);

/* Pour la version fonction */
void allouer (void **ptr, int nb, int taille)
{
 if ( (*ptr = malloc (nb*taille)) == NULL) erreur ();
}

int *ptr;
allouer (&ptr, 100, sizeof(int));


Les symboles prédéfinis


 Il existe des symboles prédéfinis pour le préprocesseur. On trouve par exemple __LINE__ et __FILE__ sont substitués par le numéro de ligne courante et le nom du fichier source courant :

#define ERREUR(msg) printf ("fichier %s ligne %d : %s", __FILE__, __LINE__, msg)

 permet d'avoir à l'exécution en plus du message d'erreur, le fichier et le numéro de ligne où s'est produite l'erreur.


Les caractères spéciaux


 Le caractère # appliqué à un argument l'entoure de guillemets :

#define DUMP(x) printf ("%s == %d", #x, x)

var = 1;
DUMP(var);

 affiche à l'exécution "var == 1" (#x a été remplacé par var).


 Le langage C dispose d'un moyen de contrôle dynamique qui permet de s'assurer qu'une condition reste toujours vérifiée à un endroit donné du programme : assert (condition)

assert (indice <= limite);

 Si indice est supérieur à limite lors de l'évaluation de l'assertion, l'exécution est interrompue et affiche un message d'erreur. Cette technique est très utile pour mettre au point un programme. Ce contrôle peut être désactivé par la définition du symbole NDEBUG. Non seulement la condition n'est plus évaluée, mais aucun code ne sera généré pour ce contrôle.


 Les caractères ## permettent de concaténer deux éléments pour n'en former qu'un seul, celui-ci pouvant à son tour être substitué par sa valeur s'il correspond à un symbole défini :

#define VAR(nom,indice) nom##indice

VAR(A,1) = 1; /* remplacé par A1 = 1; */
VAR(A,2) = 2; /* remplacé par A2 = 2; */
VAR(A,3) = 3; /* remplacé par A3 = 3; */


Objets génériques


 La puissance des macros permet de définir des objets génériques. L'exemple suivant illustre la possibilité de définir un objet pile pouvant être implémenté comme une pile d'entiers, de réels, de structures ...

 L'objet pile comprend un type structure composé d'un pointeur vers une zone de mémorisation des éléments de la pile, et ses opérations : création d'une pile de type et de taille donnée, empilage et dépilage d'éléments et destruction de la pile.

 L'objet va être défini par deux macros : une macro INTERFACE qui va réaliser la définition du type structure et la déclaration des fonctions, et une macro IMPLEMENTATION qui réalisera la définition des fonctions :

#define INTERFACE_PILE(TYPE) \
 typedef struct { TYPE *pile, *top; } Pile_##TYPE; \
 void CreerPile_##TYPE(Pile_##TYPE *, int); \
 void DetruirePile_##TYPE(Pile_##TYPE *); \
 void EmpilerPile_##TYPE(Pile_##TYPE *, TYPE); \
 TYPE DepilerPile_##TYPE(Pile_##TYPE *);

#define IMPLEMENTATION_PILE(TYPE) \
 void CreerPile_##TYPE(Pile_##TYPE *p, int dim) \
 { \
 p->pile = p->top = (TYPE *) malloc (dim * sizeof(TYPE)); \
 } \
 void DetruirePile_##TYPE(Pile_##TYPE *p) \
 { \
 free(p->pile); \
 } \
 void EmpilerPile_##TYPE(Pile_##TYPE *p, TYPE x) \
 { \
 *p->top++ = x; \
 } \
 TYPE DepilerPile_##TYPE(Pile_##TYPE *p) \
 { \
 return *(--p->top); \
 }

 Exemple d'utilisation :

#include "pile_generique.h"

INTERFACE_PILE(int)
INTERFACE_PILE(float)
IMPLEMENTATION_PILE(int)
IMPLEMENTATION_PILE(float)

main()
{
 int i;
 Pile_int p1;
 float x;
 Pile_float p2;

 CreerPile_int (&p1, 10);
 EmpilerPile_int (&p1, 1);
 ...
 i = DepilerPile_int (&p1);
 DetruirePile_int (&p1);

 CreerPile_float (&p2, 50);
 EmpilerPile_float (&p2, 2.55);
 ...
 x = DepilerPile_float (&p2);
 DetruirePile_float (&p2);
}


Téléchargez l'exemple


  Retour en haut de page Page suivante