Fondamentaux de la définition de macros
En C et C++, les macros constituent un mécanisme de substitution textuelle géré par le préprocesseur via la directive #define. Un identifiant est associé à une chaîne de caractères ; lors de la compilation, chaque occurrence de cet identifiant est remplacée par le texte défini avant même l'analyse syntaxique du code.
Il existe deux types principaux de macros :
- Macros simples :
#define NOM_MACRO VALEUR. Elles servent généralement à définir des constantes nommées. - Macros paramétrées :
#define NOM(param) (expression). Elles imitent le comportement des fonctions mais sans vérification de type.
// Exemple de macro simple
#define BUFFER_CAPACITY 1024
// Exemple de macro paramétrée
#define CALC_SQUARE(n) ((n) * (n))
Règles de visibilité et de surcharge
Contrairement aux fonctions, les macros ne supportent pas la surcharge. Si deux macros portent le même nom, la dernière définition écrase la précédente. Leur portée s'étend de la ligne de définition jusqu'à la fin de l'unité de compilation, à moins d'être explicitement annulée par #undef.
#define SET_VAL 10
#define SET_VAL 50
// Ici, SET_VAL vaudra 50 dans tout le code suivant.
#undef SET_VAL
// SET_VAL n'est plus défini après cette ligne.
Opérateurs spéciaux du préprocesseur
Trois opérateurs spécifiques permettent de manipuler les arguments des macros :
- # (Stringification) : Transforme un argument de macro en une chaîne de caractères littérale (entourée de guillemets).
- ## (Concaténation) : Fusionne deux lexèmes pour former un nouvel identifiant unique.
- #@ (Charisation) : Transforme un caractère passé en argument en un littéral de type char (spécifique à l'implémentation MSVC).
#define LOG_VAR(v) printf("La variable " #v " est utilisée\n")
#define MAKE_IDENTIFIER(prefix, suffix) prefix##_##suffix
int test_data = 100;
LOG_VAR(test_data); // Affiche : La variable test_data est utilisée
int sys_user = 5; // Créé via MAKE_IDENTIFIER(sys, user)
Mécanisme d'expansion des macros imbriquées
L'expansion des macros suit un algorithme précis, souvent source de confusion lors de l'utilisation de macros imbriquées. Les règles générales sont les suivantes :
- L'expansion s'effectue normalement de l'intérieur vers l'extérieur (les arguments sont d'abord résolus).
- Exception : Si la macro parente utilise les opérateurs
#ou##, les arguments ne sont pas expansés avant d'être substitués. - Une fois la substitution effectuée, le préprocesseur scanne à nouveau le résultat pour identifier d'autres macros à traiter.
#define WRAP_STR(x) #x
#define DEEP_EXPAND(x) WRAP_STR(x)
#define NUM_GEN(v) VALUE_##v
// Cas 1 : Expansion directe avec #
const char* s1 = WRAP_STR(NUM_GEN(1));
// Résultat : "NUM_GEN(1)" (car # empêche l'expansion de l'argument)
// Cas 2 : Expansion via une macro intermédiaire
const char* s2 = DEEP_EXPAND(NUM_GEN(1));
// Résultat : "VALUE_1" (DEEP_EXPAND n'a pas de #, donc NUM_GEN(1) est d'abord résolu)
Macros à arguments variables (Variadiques)
Le standard C99 a introduit la possibilité de définir des macros acceptant un nombre indéfini d'arugments via l'identifiant __VA_ARGS__.
Utilisation de __VA_ARGS__
Le symbole ... dans la liste des paramètres est remplacé par l'ensemble des arguments fournis lors de l'appel.
#define CUSTOM_DEBUG(format, ...) printf("[DEBUG] " format, __VA_ARGS__)
CUSTOM_DEBUG("Code erreur: %d, Message: %s\n", 404, "Non trouvé");
Extension GNU pour la suppression de virgule
Un problème classique survient lorsqu'une macro variadique est appelée sans argument additionnel : une virgule orpheline peut subsister, provoquant une erreur de compilation. L'extension GNU ##__VA_ARGS__ permet de supprimer automatiquement la virgule précédant les arguments si ceux-ci sont vides.
// Sans protection
#define M_PRINT_A(fmt, ...) printf(fmt, __VA_ARGS__)
// Avec protection (Extension GNU)
#define M_PRINT_B(fmt, ...) printf(fmt, ##__VA_ARGS__)
M_PRINT_A("Test"); // Erreur possible : printf("Test", )
M_PRINT_B("Test"); // Valide : printf("Test")
Notez que le comportement des macros peut varier légèrement selon la conformité du compilateur aux standards (C99, C11, C++11, etc.) ou l'utilisation d'extensions propriétaires.