Quelles sont les applications de l'opérateur de préprocesseur ## et des pièges à considérer?


87

Comme mentionné dans plusieurs 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 su auparavant lors de mes précédentes tentatives pour apprendre C - est l' ##opérateur du préprocesseur. Selon K&R:

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

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

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

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 à considérer?

Réponses:


47

CrashRpt: Utilisation de ## pour convertir des chaînes multi-octets de macro 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 à Lcôté de a __ 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 ce qui suit:

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

Et _Test utilisé partout dans le code


Diverses bibliothèques, utilisant pour nettoyer les noms des accesseurs et des modificateurs:

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


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;

3
Étant donné que vous pouvez concaténer des littéraux de chaîne au moment de la compilation, vous pouvez réduire l'expression BuildDate à std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__); et créer implicitement la chaîne entière à la fois.
user666412

49

Une chose à savoir lorsque vous utilisez les opérateurs de prétraitement token-paste (' ##') ou stringizing (' #') 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 passés à l'opérateur de collage de jetons sont eux-mêmes des macros, vous obtiendrez des résultats qui ne sont probablement pas ceux que vous souhaitez:

#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

1
Pour une explication de ce comportement de préprocesseur, voir stackoverflow.com/questions/8231966/…
Adam Davis

@MichaelBurr je lisais votre réponse et j'ai un doute. Comment se fait-il que cette LIGNE imprime le numéro de ligne?
HELP PLZ

3
@AbhimanyuAryan: Je ne sais pas si c'est ce que vous demandez, mais __LINE__c'est un nom de macro spécial qui est remplacé par le préprocesseur avec le numéro de ligne actuel du fichier source.
Michael Burr

Ce serait cool si les spécifications linguistiques pouvaient être citées / liées, comme ici
Antonio

14

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 ( ##) 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, on peut essayer de créer des chaînes littérales 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 non souhaités:

1 + 2 std :: vector

Les versions assez modernes de GCC (> = 3.3 ou plus) 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 des 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 GCC CPP sur la concaténation contient des informations plus utiles sur l'opérateur de collage de jetons.


Merci - je n'étais pas au courant de cela (mais je n'utilise pas trop ces opérateurs de prétraitement ...).
Michael Burr

3
C'est ce qu'on appelle l'opérateur "token colling" pour une raison - l'intention est de se retrouver avec un seul jeton lorsque vous avez terminé. Belle rédaction.
Mark Ransom

Lorsque le résultat de l'opérateur de collage de jetons n'est pas un jeton de préprocesseur valide, le comportement n'est pas défini.
alecov

Les changements de langage comme les flottants hexadécimaux, ou (en C ++) les séparateurs de chiffres et les littéraux définis par l'utilisateur, changent continuellement ce qui constitue un "jeton de prétraitement valide", alors s'il vous plaît ne jamais en abuser comme ça! Si vous devez séparer des jetons (langage propre), veuillez les épeler comme deux jetons séparés, et ne vous fiez pas aux interactions accidentelles entre la grammaire du préprocesseur et la langue proprement dite.
Kerrek SB

6

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" doit ê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;                                           \
  }

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 est de ne pas avoir à écrire les deux fn_XpmFreeAttributeset "XpmFreeAttributes"(et de risquer de mal orthographier l'un d'entre eux).


4

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 retaper sujettes aux erreurs.

Lien

Ma réponse à cette question a montré comment appliquer peu de 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 des macros ne définit pas seulement l'énumération (dans un fichier .h), elle définit également un tableau correspondant de chaînes (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 Color) dans StringTable à l'aide de l'opérateur ##. Les applications (astuces?) Comme celle-ci sont où les opérateurs # et ## sont inestimables.


3

Vous pouvez utiliser le collage de jetons lorsque vous avez besoin de 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 le parcours de liste.


2

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 de l'objet du pauvre en C droit:

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 objets «dérivés» lorsque 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".


2

SGlib utilise ## pour fondre les modèles en C. Comme 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, alors j'obtiendrais des fonctions nommées comme sglib_list_t_concat, et ainsi de suite.


2

Je l'utilise pour une assertion roulée à domicile 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 



3
Je suppose que vous entendez par «non standard» que le compilateur n'a pas fait de collage de chaînes mais a fait un collage de jetons - ou aurait-il fonctionné même sans ##?
PJTraill

1

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

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 () 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 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. Cela pourrait moi des noms de fonction (comme ci-dessus), ou des noms de champs, des noms de variables ou presque tout le reste.


1

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 tout en utilisant BITFMASK, utilisez simplement:

BITFMASK(ADDR)

0

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 les fonctions 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.


1
Qu'essayiez-vous de faire avec ça? Cela fonctionnerait aussi bien sans le "##", car il n'est pas nécessaire de coller des jetons "," dans "msg". Avez-vous essayé de stringify msg? En outre, FILE et LINE doivent être en majuscules et non en minuscules.
bk1e

Vous avez vraiment raison. J'ai besoin de trouver le script original pour voir comment ## a été utilisé. Honte à moi, pas de cookie aujourd'hui!
23
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.