web-dev-qa-db-fra.com

Quelles sont les applications de l'opérateur de préprocesseur ## et les gotchas à considérer?

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

L'opérateur de préprocesseur ## Fournit un moyen de concaténer des arguments réels lors de l'expansion de macro. Si un paramètre dans le texte de remplacement est adjacent à un ##, Le paramètre est remplacé par l'argument réel, le ## Et l'espace blanc environnant sont supprimés et le résultat est analysé à nouveau. Par exemple, la macro paste concatène ses deux arguments:

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

donc paste(name, 1) crée le jeton 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?

86
John Rudy

CrashRpt: utilisation de ## pour convertir des chaînes de macro multi-octets en Unicode

Une utilisation intéressante dans 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 probablement 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.

Mettre 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


Diverses bibliothèques, utilisant des noms d'accesseurs et de modificateurs propres:

Je l'ai également vu utilisé dans le code pour définir les accesseurs et les 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 intelligente.


Diverses bibliothèques, 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;
46
Brian R. Bondy

Une chose à savoir lorsque vous utilisez le token-paste ('## ') ou de mise en chaîne (' # ') les opérateurs de prétraitement 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 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__)));
}

Le résultat:

buggy results:
SOME_MACRO__LINE__
BAD_PASTE( SOME_MACRO, __LINE__)
PASTE( SOME_MACRO, __LINE__)

desired result:
function_name21
49
Michael Burr

Voici un problème que j'ai rencontré lors de la mise à niveau vers une nouvelle version d'un compilateur:

Utilisation inutile 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 nuisible.

Par exemple, on pourrait essayer de construire des littéraux de chaîne au moment 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 environ) ne parviendront pas à compiler 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 documentation GCC CPP sur la concaténation contient des informations plus utiles sur l'opérateur de collage de jetons.

14
bk1e

Ceci est utile dans toutes sortes de situations afin de ne pas se 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" doit être affectée à 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;                                           \
  }

On peut alors l'utiliser:

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

L'avantage n'est pas d'avoir à écrire les deux fn_XpmFreeAttributes et "XpmFreeAttributes" (et risquer de mal orthographier l'un d'eux).

6
Vebjorn Ljosa

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

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'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 (c'est-à-dire de la couleur) dans StringTable à l'aide de l'opérateur ##. Les applications (astuces?) Comme celle-ci sont celles où les opérateurs # et ## sont inestimables.

4
Bill Forster

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 la traversée de liste.

2
qrdl

Je l'utilise dans les programmes C pour aider à appliquer correctement les prototypes pour un ensemble de méthodes qui doivent se conformer à une sorte de convention d'appel. D'une certaine manière, cela peut être utilisé pour l'orientation d'objet du pauvre homme en C droit:

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 );

Cela applique un paramétrage correct pour tous les objets "dérivés" lorsque vous effectuez:

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".

2
Tall Jeff

SGlib utilise ## pour truquer les modèles en C. Parce qu'il n'y a pas de surcharge de fonction, ## 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 comme sglib_list_t_concat, et ainsi de suite.

2
Jack

Je l'utilise pour une assertion roulée à la maison sur un compilateur C non standard pour embarqué:



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

2
c0m4

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)
1
Keshava GN

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

UNITTEST(test_name)

s'étend à:

void __testframework_test_name ()
1
John Millikin

L'utilisation principale est lorsque vous avez une convention de dénomination et que vous souhaitez que votre macro tire parti de cette convention de dénomination. Peut-être avez-vous plusieurs familles de méthodes: image_create (), image_activate () et image_release () également 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 objets:

#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 la seule sorte de convention de dénomination à laquelle cela s'applique - presque la grande majorité des conventions de dénomination utilisent une sous-chaîne commune pour former les noms. Il peut s'agir de noms de fonctions (comme ci-dessus), ou de noms de champs, de noms de variables ou de presque tout le reste.

1
mcherm

Il 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 fonction et func:

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

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

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

0
ya23