Frage

Wie in vielen meinen früheren Fragen erwähnt, ich arbeite durch K & R, und bin zur Zeit in den Prä-Prozessor. Eines der interessantesten Dinge - etwas, das ich nie zuvor von keinem meiner früheren Versuchen wusste C zu lernen - ist der ## Preprozessor-Operator. Nach K & R:

  

Der Präprozessor Operator ##   bietet eine Möglichkeit, tatsächlich verketten   Argumente während Makroerweiterung. Wenn ein   Parameter im Ersetzungstext   benachbart zu einem ## ist der Parameter   durch das tatsächliche Argument ersetzt, die   ## und die umliegende weißer Raum ist   entfernt, und das Ergebnis wird erneut gescannt.   Zum Beispiel kann das Makro paste   verkettet seine zwei Argumente:

     

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

     

so schafft paste(name, 1) das Token   name1.

Wie und warum jemand würde dies in der realen Welt verwenden? Was sind praktische Beispiele für seine Verwendung, und gibt es zu berücksichtigen gotchas?

War es hilfreich?

Lösung

CrashRpt: ## Verwenden von Makro Multi-Byte-Zeichenketten in Unicode zu konvertieren

Eine interessante Verwendung in CrashRpt (Crash-Reporting-Bibliothek) ist die folgende:

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

Hier wollen sie eine Zwei-Byte-Zeichenfolge verwenden, anstelle eines Ein-Byte-pro-Zeichen-String. Dies ist wahrscheinlich sieht aus wie es wirklich sinnlos ist, aber sie tun es für einen guten Grund.

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

Sie benutzen es mit einem anderen Makro, die eine Zeichenfolge mit dem Datum und die Zeit zurück.

Putting L neben einem __ DATE __ würden Sie einen Übersetzungsfehler.


Fenster: Mit ## für allgemeine Unicode oder Multi-Byte-Zeichenketten

Windows verwendet in etwa wie folgt:

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

Und _T wird überall im Code


Verschiedene Bibliotheken verwendet für saubere Accessor und Modifikator-Namen:

Ich habe es auch in Code verwendet gesehen zu definieren Zugriffs- und Modifikatoren:

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

Ebenso können Sie das gleiche Verfahren für alle anderen Arten von cleveren Namenserstellung verwenden.


Verschiedene Bibliotheken, es zu benutzen, um mehrere Variablendeklarationen zu machen:

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

Andere Tipps

Eine Sache bewusst zu sein, wenn Sie die Token-Paste verwenden ( ‚##‘) oder Zeichenfolgen ( ‚#‘) Vorverarbeitung Betreiber ist, dass Sie eine zusätzliche Dereferenzierungsebene verwenden müssen für sie in alle richtig funktioniert Fälle.

Wenn Sie dies nicht tun und die Elemente auf dem Token-Einfügen Operator sind Makros selbst übergeben, werden Sie Ergebnisse, die wahrscheinlich nicht, was Sie wollen:

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

Der Ausgang:

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

desired result:
function_name21

Hier ist ein Gotcha, die ich lief in, wenn auf eine neue Version eines Compilers Upgrade:

Unnötige Verwendung des Token-Einfügen-Operators (##) nicht tragbar und kann unerwünschte Leerzeichen, Warnungen oder Fehler erzeugen.

Wenn das Ergebnis des Token-Einfügen Operators keine gültigen Präprozessor-Token ist, der Token-Einfügen von Operator ist unnötig und möglicherweise schädlich.

Zum Beispiel könnte man versuchen Stringliterale bei der Kompilierung mit dem Token-Einfügen von Operator zu erstellen:

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

Bei einigen Compilern, dieser Wille Ausgang das erwartete Ergebnis:

1+2 std::vector

Auf anderen Compilern wird diese unerwünschte Leerzeichen enthalten:

1 + 2 std :: vector

Ziemlich moderne Versionen von GCC (> = 3.3 oder so) fehl diesen Code zu kompilieren:

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

Die Lösung ist das Token-Einfügen Operator wegzulassen, wenn Präprozessor verketten Token C / C ++ Operatoren:

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

Die GCC CPP-Dokumentation Kapitel über die Verkettung hat weitere nützliche Informationen über den Token-Einfügen-Operator.

Dies ist in allen möglichen Situationen nützlich, um sie nicht unnötig zu wiederholen. Im Folgenden ist ein Beispiel aus dem Emacs-Quellcode. Wir möchten, dass eine Reihe von Funktionen aus einer Bibliothek laden. Die Funktion „foo“ sollte fn_foo zugeordnet werden, und so weiter. Wir definieren das folgende Makro:

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

Wir können es dann verwenden:

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

Der Vorteil aufweist, ist nicht sowohl fn_XpmFreeAttributes und "XpmFreeAttributes" (und Risiko misspelling einer von ihnen) zu schreiben.

Eine vorherige Frage auf Stack-Überlauf bat um eine glatte Methode ohne viele fehleranfällige Abtippen Stringdarstellungen für Enumerationskonstanten zu erzeugen.

Link-

Meine Antwort auf diese Frage zeigte, wie wenig Präprozessor Magie anwenden können Sie Ihre Aufzählung wie folgt definieren (zum Beispiel) ...;

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

... Mit dem Vorteil, dass die Makroerweiterung nicht nur die Aufzählung definiert (in einer H-Datei), es definiert auch eine passende Array von Strings (in einer C-Datei);

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

Der Name der String-Tabelle stammt aus dem Makroparameter Einfügen (das heißt Farbe) zu String den ##-Operator. Anwendungen (Tricks?) Wie diese sind, wo die # und ## Operatoren sind von unschätzbarem Wert.

Ich benutze es in C-Programmen richtig zu helfen, die Prototypen für eine Reihe von Methoden zu erzwingen, die zu einer Art Aufrufkonvention entspricht. In gewisser Weise kann dies für arme Menschen Objektorientierung in geraden C verwendet werden:

SCREEN_HANDLER( activeCall )

erweitert, um so etwas wie folgt aus:

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

Dies erzwingt die korrekte Parametrierung für alle „abgeleiteten“ Objekte, wenn Sie tun:

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

die oben in Ihren Header-Dateien, usw. Es ist auch für die Wartung nützlich ist, wenn man auch die Definitionen und / oder fügen Sie Methoden, um die „Objekte“.

Änderungen passieren wollen

SGlib verwendet ## auf im Grunde Fudge Vorlagen in C. Da es keine Funktion Überlastung ist, ## verwendet wird, die Typnamen in den Namen der generierten Funktionen zu kleben. Wenn ich eine Liste Typen namens list_t hatte, dann würde ich Funktionen wie sglib_list_t_concat genannt werden, und so weiter.

Ich benutze es für ein Haus gerollt assert auf einem Nicht-Standard-C-Compiler für eingebettete:



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


Sie token einfügen verwenden können, wenn Sie Makroparameter mit etwas anderem verketten müssen.

Es kann für Vorlagen verwendet werden:

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

In diesem Fall LINKED_LIST (int) würden Sie

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

Ebenso können Sie eine Funktion Vorlage für Liste Traversal schreiben.

Ich benutze es für das Hinzufügen von benutzerdefinierten Präfixen Variablen durch Makros definiert. So etwas wie:

UNITTEST(test_name)

erweitert zu:

void __testframework_test_name ()

Die Hauptanwendung ist, wenn Sie eine Namenskonvention haben und Sie möchten Ihr Makro Vorteil dieser Namenskonvention zu nehmen. Vielleicht haben Sie mehrere Familien von Methoden: image_create (), image_activate () und image_release () auch file_create (), file_activate (), file_release () und mobile_create (), mobile_activate () und mobile_release ()

.

Sie können einen Makro für den Umgang mit Objektlebenszyklus schreiben:

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

Natürlich, eine Art „Minimalversion von Objekten“ ist nicht die einzige Art von Namenskonvention dies gilt - fast die überwiegende Mehrheit der Namenskonventionen Verwendung eines gemeinsamen Unterkette macht die Namen zu bilden. Es könnte mir Funktionsnamen (wie oben) oder Feldnamen, Variablennamen, oder die meisten alles andere.

Eine wichtige Anwendung in WinCE:

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

Bei der Definition Register Bitbeschreibung tun wir das Folgen:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

Und bei der Verwendung von BITFMASK, verwenden Sie einfach:

BITFMASK(ADDR)

Es ist sehr nützlich für die Protokollierung. Sie tun können:

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

Oder, wenn Ihr Compiler nicht unterstützt Funktion und func :

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

Die oben „Funktionen“ protokolliert Meldungen und zeigt genau, welche Funktion eine Nachricht protokolliert.

Meine C ++ Syntax vielleicht nicht ganz korrekt sein.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top