Question

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

  

L'opérateur de préprocesseur ##   fournit un moyen de concaténer réelle   arguments pendant l'expansion de la macro. Si un   paramètre dans le texte de remplacement est   adjacent à un ## , le paramètre est   remplacé par l'argument actuel, le    ## et l'espace blanc environnant sont   supprimé, et le résultat est à nouveau scanné.   Par exemple, la macro coller   concatène ses deux arguments:

     

#define coller (avant, arrière) avant ## retour

     

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

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

Était-ce utile?

La solution

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

Une utilisation intéressante de CrashRpt (bibliothèque de rapports d’incidents) 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 ressemble probablement à cela est vraiment inutile, mais ils le font pour une bonne raison.

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

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

Placer L à côté d'un __ DATE __ vous donnerait une erreur de compilation.

Windows: Utilisation de ## pour les chaînes génériques Unicode 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

Plusieurs bibliothèques utilisant des noms d'accès et de modificateurs propres:

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 n’importe quel autre type de création de nom intelligent.

Différentes bibliothèques l'utilisant pour effectuer plusieurs déclarations de variable à la fois:

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

Autres conseils

Une chose à savoir lorsque vous utilisez les opérateurs de prétraitement avec jeton-coller (' ## ") ou stringizing (' # ') est que vous doivent utiliser un niveau d'indirection supplémentaire pour fonctionner 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 des résultats qui ne sont probablement pas ce que vous voulez:

#include <stdio.h>

#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__)));
}

La sortie:

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 non nécessaire de l'opérateur de collage de jetons ( ## ) n'est pas 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 jetons est inutile et peut-être dangereux.

Par exemple, vous pouvez essayer de créer des littéraux de chaîne lors de la compilation à l'aide de 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 inclut les espaces non désirés:

1 + 2 std :: vector

Les versions assez modernes de GCC (> = 3,3 environ) ne compileront pas 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 aux 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 Le chapitre de la documentation de GCC CPP sur la concaténation a 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 tiré du code source d'Emacs. Nous aimerions charger un certain nombre de fonctions depuis une bibliothèque. La fonction " foo " doit être assigné à fn_foo , etc. 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 ensuite l'utiliser:

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

L'avantage est de ne pas avoir à écrire à la fois fn_XpmFreeAttributes et "XpmFreeAttributes" (et risquer de mal les épeler).

Une question précédente sur Stack Overflow demandait une méthode fluide pour générer des représentations de chaîne pour les constantes d’énumération sans beaucoup de retypage source d’erreurs.

Lien

Ma réponse à cette question a montré comment appliquer 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 )

... L'avantage étant que l'extension de 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 *ColorStringTable[] =
{
  "RED",
  "GREEN",
  "BLUE"
};

Le nom de la table de chaînes provient du collage du paramètre de macro (par exemple, Color) dans StringTable à l'aide de l'opérateur ##. Des applications (astuces?) Comme celle-ci sont où les opérateurs # et ## sont inestimables.

Je l’utilise dans les programmes C pour aider à appliquer correctement les prototypes pour un ensemble de méthodes devant respecter une sorte de convention d’appel. D'une certaine manière, cela peut être utilisé pour l'orientation des objets du pauvre homme en C:

SCREEN_HANDLER( activeCall )

se développe à quelque chose comme ceci:

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

Ceci applique le paramétrage correct pour tous les "dérivés". objets quand vous 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 ajouter des méthodes aux "objets".

SGlib utilise ## pour falsifier les modèles en C. En l'absence de surcharge des fonctions, ## est utilisé 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 un assert roulé à la maison sur un compilateur C non standard pour imbriqué:



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


Vous pouvez utiliser le collage de jetons lorsque vous devez concaténer des paramètres 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 parcourir la liste.

Je l'utilise pour ajouter des préfixes personnalisés à des variables définies par des macros. Donc, quelque chose comme:

UNITTEST(test_name)

se développe en:

void __testframework_test_name ()

L’utilisation principale est lorsque vous avez une convention de dénomination et que vous souhaitez que votre macro tire parti de cette convention. 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 d'un objet:

#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 objets". n’est pas le seul type de convention de dénomination auquel cette règle s’applique - presque la grande majorité des conventions de dénomination utilisent une sous-chaîne commune pour former les noms. Cela pourrait me donner des noms de fonction (comme ci-dessus), ou des noms de champs, des noms de variables ou la plupart d’autres choses.

Une utilisation importante dans WinCE:

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

Lors de la définition de la description du bit de registre, nous procédons comme suit:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

Et lorsque vous utilisez BITFMASK, utilisez simplement:

BITFMASK(ADDR)

C'est très utile pour la journalisation. Vous pouvez faire:

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

Ou, si votre compilateur ne prend pas en charge les fonctions et func :

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

Les "fonctions" ci-dessus enregistre le message et indique exactement quelle fonction a enregistré un message.

Ma syntaxe C ++ n'est peut-être pas tout à fait correcte.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top