Quelles sont les applications de l’opérateur de préprocesseur ## et des pièges à prendre en compte?

Comme mentionné dans plusieurs de mes questions précédentes, je travaille avec K & R et je suis actuellement dans le préprocesseur. L’une des choses les plus intéressantes – quelque chose que je n’ai jamais su auparavant de mes précédentes tentatives pour apprendre le C – est l’opérateur de préprocesseur ## . Selon K & R:

L’opérateur de préprocesseur ## permet de concaténer les arguments réels lors de l’expansion de la macro. Si un paramètre dans le texte de remplacement est adjacent à ## , le paramètre est remplacé par l’argument réel, les espaces ## et blancs environnants sont supprimés et le résultat est à nouveau analysé. Par exemple, la paste macro concatène ses deux arguments:

#define paste(front, back) front ## back

so paste(name, 1) crée le name1 .

Comment et pourquoi quelqu’un utiliserait-il cela dans le monde réel? Quels sont les exemples pratiques de son utilisation, et y a-t-il des pièges à considérer?

CrashRpt: Utilisation de ## pour convertir des chaînes de plusieurs octets de macro en Unicode

Une utilisation intéressante de CrashRpt (bibliothèque de rapports de plantage) est la suivante:

 #define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) //Note you need a WIDEN2 so that __DATE__ will evaluate first. 

Ici, ils veulent utiliser une chaîne de deux octets au lieu d’une chaîne d’un octet par caractère. Cela semble sans doute être vraiment inutile, mais ils le font pour une bonne raison.

  std::wssortingng BuildDate = std::wssortingng(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__); 

Ils l’utilisent avec une autre macro qui renvoie une chaîne avec la date et l’heure.

Mettre L côté d’une __ DATE __ vous donnerait une erreur de compilation.


Windows: Utilisation de ## pour les chaînes Unicode génériques ou multi-octets

Windows utilise quelque chose comme ceci:

 #ifdef _UNICODE #define _T(x) L ## x #else #define _T(x) x #endif 

Et _T est utilisé partout dans le code


Diverses bibliothèques, utilisant pour les noms d’accesseur et de modificateur:

Je l’ai également vu utilisé dans le code pour définir des accesseurs et des modificateurs:

 #define MYLIB_ACCESSOR(name) (Get##name) #define MYLIB_MODIFIER(name) (Set##name) 

De même, vous pouvez utiliser cette même méthode pour tout autre type de création de nom intelligent.


Diverses bibliothèques, en l’utilisant pour faire plusieurs déclarations de variables à la fois:

 #define CREATE_3_VARS(name) name##1, name##2, name##3 int CREATE_3_VARS(myInts); myInts1 = 13; myInts2 = 19; myInts3 = 77; 

Une chose à prendre en compte lorsque vous utilisez les opérateurs de prétraitement de type token-paste (‘ ## ‘) ou ssortingngizing (‘ # ‘) est que vous devez utiliser un niveau supplémentaire d’indirection pour qu’ils fonctionnent correctement dans tous les cas.

Si vous ne le faites pas et que les éléments transmis à l’opérateur de collage de jetons sont eux-mêmes des macros, vous obtiendrez probablement des résultats qui ne vous conviennent pas:

 #include  #define STRINGIFY2( x) #x #define STRINGIFY(x) STRINGIFY2(x) #define PASTE2( a, b) a##b #define PASTE( a, b) PASTE2( a, b) #define BAD_PASTE(x,y) x##y #define BAD_STRINGIFY(x) #x #define SOME_MACRO function_name int main() { printf( "buggy results:\n"); printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__))); printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__))); printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__))); printf( "\n" "desired result:\n"); printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__))); } 

Le résultat:

 buggy results: SOME_MACRO__LINE__ BAD_PASTE( SOME_MACRO, __LINE__) PASTE( SOME_MACRO, __LINE__) desired result: function_name21 

Voici un piège que j’ai rencontré lors de la mise à niveau vers une nouvelle version d’un compilateur:

L’utilisation inutile de l’opérateur de collage de jetons ( ## ) est non portable et peut générer des espaces, des avertissements ou des erreurs indésirables.

Lorsque le résultat de l’opérateur de collage de jetons n’est pas un jeton de préprocesseur valide, l’opérateur de collage de jeton est inutile et peut-être dangereux.

Par exemple, on peut essayer de construire des littéraux de chaîne lors de la compilation en utilisant l’opérateur de collage de jetons:

 #define STRINGIFY(x) #x #define PLUS(a, b) STRINGIFY(a##+##b) #define NS(a, b) STRINGIFY(a##::##b) printf("%s %s\n", PLUS(1,2), NS(std,vector)); 

Sur certains compilateurs, cela produira le résultat attendu:

 1+2 std::vector 

Sur d’autres compilateurs, cela inclura des espaces indésirables:

 1 + 2 std :: vector 

Les versions assez modernes de GCC (> = 3.3 ou plus) ne pourront pas comstackr ce code:

 foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token 

La solution consiste à omettre l’opérateur de collage de jetons lors de la concaténation de jetons de préprocesseur en opérateurs C / C ++:

 #define STRINGIFY(x) #x #define PLUS(a, b) STRINGIFY(a+b) #define NS(a, b) STRINGIFY(a::b) printf("%s %s\n", PLUS(1,2), NS(std,vector)); 

Le chapitre de la documentation de GCC CPP sur la concaténation contient des informations plus utiles sur l’opérateur de collage de jetons.

Ceci est utile dans toutes sortes de situations afin de ne pas vous répéter inutilement. Voici un exemple du code source d’Emacs. Nous aimerions charger un certain nombre de fonctions à partir d’une bibliothèque. La fonction “foo” devrait être assignée à fn_foo , et ainsi de suite. Nous définissons la macro suivante:

 #define LOAD_IMGLIB_FN(lib,func) { \ fn_##func = (void *) GetProcAddress (lib, #func); \ if (!fn_##func) return 0; \ } 

Nous pouvons alors l’utiliser:

 LOAD_IMGLIB_FN (library, XpmFreeAtsortingbutes); LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer); LOAD_IMGLIB_FN (library, XpmReadFileToImage); LOAD_IMGLIB_FN (library, XImageFree); 

L’avantage n’est pas d’avoir à écrire à la fois fn_XpmFreeAtsortingbutes et "XpmFreeAtsortingbutes" (et de risquer de mal orthographier l’un d’eux).

Une question précédente sur Stack Overflow a demandé une méthode fluide de génération de représentations de chaîne pour les constantes d’énumération sans avoir à effectuer beaucoup de retouches.

Lien

Ma réponse à cette question a montré comment l’application d’une petite magie de préprocesseur vous permet de définir votre énumération comme ceci (par exemple) …;

 ENUM_BEGIN( Color ) ENUM(RED), ENUM(GREEN), ENUM(BLUE) ENUM_END( Color ) 

… Avec l’avantage que l’expansion de la macro ne définit pas seulement l’énumération (dans un fichier .h), elle définit également un tableau de chaînes correspondant (dans un fichier .c);

 const char *ColorSsortingngTable[] = { "RED", "GREEN", "BLUE" }; 

Le nom de la table de chaînes provient du collage du paramètre macro (c’est-à-dire Color) avec SsortingngTable à l’aide de l’opérateur ##. Les applications (astuces?) Comme celle-ci sont où les opérateurs # et ## sont inestimables.

Vous pouvez utiliser le collage de jetons lorsque vous devez concaténer des parameters de macro avec autre chose.

Il peut être utilisé pour les modèles:

 #define LINKED_LIST(A) struct list##_##A {\ A value; \ struct list##_##A *next; \ }; 

Dans ce cas, LINKED_LIST (int) vous donnerait

 struct list_int { int value; struct list_int *next; }; 

De même, vous pouvez écrire un modèle de fonction pour la traversée de liste.

Je l’utilise dans les programmes C pour aider à appliquer correctement les prototypes pour un ensemble de méthodes qui doivent être conformes à une sorte de convention d’appel. En un sens, cela peut être utilisé pour l’orientation de l’object du pauvre homme en C:

 SCREEN_HANDLER( activeCall ) 

se développe en quelque chose comme ceci:

 STATUS activeCall_constructor( HANDLE *pInst ) STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent ); STATUS activeCall_destructor( HANDLE *pInst ); 

Cela impose un paramétrage correct pour tous les objects “dérivés” lorsque vous le faites:

 SCREEN_HANDLER( activeCall ) SCREEN_HANDLER( ringingCall ) SCREEN_HANDLER( heldCall ) 

Ce qui précède dans vos fichiers d’en-tête, etc. Il est également utile pour la maintenance si vous souhaitez même modifier les définitions et / ou append des méthodes aux “objects”.

SGlib utilise ## pour fondre les templates en C. Comme il n’y a pas de surcharge de fonctions, ## est utilisé pour coller le nom du type dans les noms des fonctions générées. Si j’avais un type de liste appelé list_t, j’obtiendrais des fonctions nommées sglib_list_t_concat, etc.

Je l’utilise pour une affirmation maison sur un compilateur C non standard pour incorporé:

#define ASSERT(exp) if(!(exp)){ \ print_to_rs232("Assert failed: " ## #exp );\ while(1){} //Let the watchdog kill us
#define ASSERT(exp) if(!(exp)){ \ print_to_rs232("Assert failed: " ## #exp );\ while(1){} //Let the watchdog kill us 

Je l’utilise pour append des préfixes personnalisés aux variables définies par les macros. Donc quelque chose comme:

 UNITTEST(test_name) 

s’étend à:

 void __testframework_test_name () 

L’utilisation principale est lorsque vous avez une convention de dénomination et que vous souhaitez que votre macro profite de cette convention de dénomination. Vous avez peut-être plusieurs familles de méthodes: image_create (), image_activate () et image_release () ainsi que file_create (), file_activate (), file_release () et mobile_create (), mobile_activate () et mobile_release ().

Vous pouvez écrire une macro pour gérer le cycle de vie des objects:

 #define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release()) 

Bien sûr, une sorte de “version minimale des objects” n’est pas la seule convention de dénomination à laquelle cela s’applique – presque la grande majorité des conventions de nommage utilisent une sous-chaîne commune pour former les noms. Cela pourrait me permettre de nommer des noms (comme ci-dessus), ou des noms de champs, des noms de variables ou la plupart du temps.

Une utilisation importante dans WinCE:

 #define BITFMASK(bit_position) (((1U < < (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT)) 

Tout en définissant la description du bit de registre, nous faisons ce qui suit:

 #define ADDR_LEFTSHIFT 0 #define ADDR_WIDTH 7 

Et en utilisant BITFMASK, utilisez simplement:

 BITFMASK(ADDR) 

C’est très utile pour la journalisation. Tu peux faire:

 #define LOG(msg) log_msg(__function__, ## msg) 

Ou, si votre compilateur ne prend pas en charge la fonction et le func :

 #define LOG(msg) log_msg(__file__, __line__, ## msg) 

Le “fonctions” ci-dessus enregistre le message et montre exactement quelle fonction a enregistré un message.

Ma syntaxe C ++ pourrait ne pas être tout à fait correcte.