Comprendre la récursion des macros pour les arguments de macro
En programmation C, les macros offrent un outil puissant pour la manipulation de texte. Un aspect intéressant est la possibilité d’utiliser des macros sur les arguments d’autres macros. Cependant, cela présente un défi technique, car les macros récursives ne sont généralement pas autorisées dans le langage.
Le problème : les macros récursives
Considérez le scénario dans lequel nous souhaitons créer une macro foreach, nommée PRINT_ALL, qui applique une macro donnée, PRINT, à une liste d'arguments. Par exemple :
int a = 1, b = 3, d = 0; PRINT_ALL(a,b,d);
Cela invoquerait la macro PRINT sur chacune des variables a, b et d. L'approche naïve pourrait employer une macro récursive, comme suit :
#define FIRST_ARG(arg,...) arg #define AFTER_FIRST_ARG(arg,...) , ##__VA_ARGS__ #define PRINT(a) printf(#a": %d", a) #define PRINT_ALL PRINT(FIRST_ARG(__VA_ARGS__)); PRINT_ALL(AFTER_FIRST_ARG(__VA_ARGS__))
Cependant, cette approche pose deux problèmes : les macros ne peuvent pas s'appeler de manière récursive, et il lui manque une condition d'arrêt pour arrêter la récursion.
Une solution de contournement récursive
Pour surmonter ces obstacles, une solution de contournement intelligente exploite une technique connue sous le nom de macro-évaluation-récursion. L'idée clé est d'émettre un texte de macro qui simule un appel de macro sans réellement invoquer la macro elle-même.
Considérez la macro suivante :
#define MAP_OUT
Si nous avons les macros suivantes :
#define A(x) x B MAP_OUT (x) #define B(x) x A MAP_OUT (x)
L'évaluation de la macro A(blah) produit le texte de sortie :
blah B (blah)
Ce texte sert de Espace réservé de remplacement de macro. Il peut être renvoyé au préprocesseur pour être étendu davantage, poursuivant ainsi le processus d'évaluation de la macro.
Pour faciliter cette récursivité, une série de macros EVAL sont définies :
#define EVAL0(...) __VA_ARGS__ #define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__))) #define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__))) #define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__))) #define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__))) #define EVAL(...)EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
Chaque macro s'applique plusieurs niveaux d'évaluation, amplifiant ainsi l'effet des macros appliquées.
Arrêter le Récursion
Pour contrôler la récursion, une macro spéciale, MAP_END, est définie :
#define MAP_END(...)
L'évaluation de cette macro ne fait rien, mettant effectivement fin à la récursion.
Le prochain défi consiste à déterminer quand utiliser MAP_END au lieu de poursuivre la récursion. Pour y parvenir, une macro MAP_NEXT compare un élément de liste à un marqueur spécial de fin de liste. S'ils correspondent, il renvoie MAP_END ; sinon, il renvoie le paramètre suivant :
#define MAP_GET_END() 0, MAP_END #define MAP_NEXT0(item, next, ...) next MAP_OUT #define MAP_NEXT1(item, next) MAP_NEXT0(item, next,0) #define MAP_NEXT(item, next) MAP_NEXT1(MAP_GET_END item, next)
En construisant soigneusement la macro MAP_NEXT, nous pouvons contrôler si la récursion continue ou se termine.
Implémentation finale
En combinant ces éléments de base, nous pouvons créer la macro MAP qui parcourt une liste et applique une macro donnée à chacune item :
#define MAP(f,...)EVAL(MAP1(f,__VA_ARGS__,(),0))
Cette macro fonctionne en plaçant un marqueur de fin de liste à la fin de la liste, ainsi qu'un argument supplémentaire pour garantir la conformité ANSI. Il transmet ensuite la liste via plusieurs appels de macro EVAL et renvoie le résultat.
Cette technique fournit une solution créative au problème de l'utilisation de macros sur des arguments de macro. Il permet des capacités sophistiquées de manipulation de macros, permettant aux programmeurs d'étendre les fonctionnalités du préprocesseur de manière innovante.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!