Ich habe gerade erfahren X-Makros. Welche realen Anwendungen von X-Makros haben Sie gesehen? Wann sind sie das richtige Werkzeug für den Job?
Reale Verwendung von X-Makros
Agnius Wassilauskas
ACRL
Ich habe X-Makros vor ein paar Jahren entdeckt, als ich anfing, Funktionszeiger in meinem Code zu verwenden. Ich bin ein eingebetteter Programmierer und verwende häufig Zustandsmaschinen. Oft würde ich Code wie diesen schreiben:
/* declare an enumeration of state codes */
enum{ STATE0, STATE1, STATE2, ... , STATEX, NUM_STATES};
/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {func0, func1, func2, ... , funcX};
Das Problem war, dass ich es für sehr fehleranfällig hielt, die Reihenfolge meiner Funktionszeigertabelle so beibehalten zu müssen, dass sie mit der Reihenfolge meiner Aufzählung von Zuständen übereinstimmte.
Ein Freund von mir machte mich mit X-Makros bekannt und es war, als ob eine Glühbirne in meinem Kopf anging. Im Ernst, wo warst du mein ganzes Leben x-Makros!
Also definiere ich jetzt die folgende Tabelle:
#define STATE_TABLE \
ENTRY(STATE0, func0) \
ENTRY(STATE1, func1) \
ENTRY(STATE2, func2) \
...
ENTRY(STATEX, funcX) \
Und ich kann es wie folgt verwenden:
enum
{
#define ENTRY(a,b) a,
STATE_TABLE
#undef ENTRY
NUM_STATES
};
und
p_func_t jumptable[NUM_STATES] =
{
#define ENTRY(a,b) b,
STATE_TABLE
#undef ENTRY
};
Als Bonus kann ich den Präprozessor meine Funktionsprototypen auch wie folgt erstellen lassen:
#define ENTRY(a,b) static void b(void);
STATE_TABLE
#undef ENTRY
Eine weitere Verwendung besteht darin, Register zu deklarieren und zu initialisieren
#define IO_ADDRESS_OFFSET (0x8000)
#define REGISTER_TABLE\
ENTRY(reg0, IO_ADDRESS_OFFSET + 0, 0x11)\
ENTRY(reg1, IO_ADDRESS_OFFSET + 1, 0x55)\
ENTRY(reg2, IO_ADDRESS_OFFSET + 2, 0x1b)\
...
ENTRY(regX, IO_ADDRESS_OFFSET + X, 0x33)\
/* declare the registers (where _at_ is a compiler specific directive) */
#define ENTRY(a, b, c) volatile uint8_t a _at_ b:
REGISTER_TABLE
#undef ENTRY
/* initialize registers */
#define ENTRY(a, b, c) a = c;
REGISTER_TABLE
#undef ENTRY
Meine bevorzugte Verwendung ist jedoch, wenn es um Kommunikationshandler geht
Zuerst erstelle ich eine Kommunikationstabelle, die jeden Befehlsnamen und -code enthält:
#define COMMAND_TABLE \
ENTRY(RESERVED, reserved, 0x00) \
ENTRY(COMMAND1, command1, 0x01) \
ENTRY(COMMAND2, command2, 0x02) \
...
ENTRY(COMMANDX, commandX, 0x0X) \
Ich habe sowohl Groß- als auch Kleinbuchstaben in der Tabelle, da Großbuchstaben für Aufzählungen und Kleinbuchstaben für Funktionsnamen verwendet werden.
Dann definiere ich auch Strukturen für jeden Befehl, um zu definieren, wie jeder Befehl aussieht:
typedef struct {...}command1_cmd_t;
typedef struct {...}command2_cmd_t;
etc.
Ebenso definiere ich Strukturen für jede Befehlsantwort:
typedef struct {...}command1_resp_t;
typedef struct {...}command2_resp_t;
etc.
Dann kann ich meine Befehlscode-Aufzählung definieren:
enum
{
#define ENTRY(a,b,c) a##_CMD = c,
COMMAND_TABLE
#undef ENTRY
};
Ich kann meine Befehlslängenaufzählung definieren:
enum
{
#define ENTRY(a,b,c) a##_CMD_LENGTH = sizeof(b##_cmd_t);
COMMAND_TABLE
#undef ENTRY
};
Ich kann meine Antwortlängenaufzählung definieren:
enum
{
#define ENTRY(a,b,c) a##_RESP_LENGTH = sizeof(b##_resp_t);
COMMAND_TABLE
#undef ENTRY
};
Ich kann bestimmen, wie viele Befehle es gibt, wie folgt:
typedef struct
{
#define ENTRY(a,b,c) uint8_t b;
COMMAND_TABLE
#undef ENTRY
} offset_struct_t;
#define NUMBER_OF_COMMANDS sizeof(offset_struct_t)
HINWEIS: Ich instanziiere offset_struct_t nie wirklich, ich benutze es nur als eine Möglichkeit für den Compiler, meine Definition der Anzahl der Befehle für mich zu generieren.
Beachten Sie, dass ich meine Tabelle mit Funktionszeigern wie folgt generieren kann:
p_func_t jump_table[NUMBER_OF_COMMANDS] =
{
#define ENTRY(a,b,c) process_##b,
COMMAND_TABLE
#undef ENTRY
}
Und meine Funktionsprototypen:
#define ENTRY(a,b,c) void process_##b(void);
COMMAND_TABLE
#undef ENTRY
Jetzt endlich für die coolste Verwendung überhaupt, ich kann den Compiler berechnen lassen, wie groß mein Sendepuffer sein sollte.
/* reminder the sizeof a union is the size of its largest member */
typedef union
{
#define ENTRY(a,b,c) uint8_t b##_buf[sizeof(b##_cmd_t)];
COMMAND_TABLE
#undef ENTRY
}tx_buf_t
Auch diese Union ist wie meine Offset-Struktur, sie wird nicht instanziiert, stattdessen kann ich den sizeof-Operator verwenden, um meine Übertragungspuffergröße zu deklarieren.
uint8_t tx_buf[sizeof(tx_buf_t)];
Jetzt hat mein Sendepuffer tx_buf die optimale Größe und wenn ich diesem Kommunikations-Handler Befehle hinzufüge, wird mein Puffer immer die optimale Größe haben. Kühl!
Eine andere Verwendung ist das Erstellen von Offset-Tabellen: Da der Speicher bei eingebetteten Systemen oft eine Einschränkung darstellt, möchte ich nicht 512 Bytes für meine Sprungtabelle verwenden (2 Bytes pro Zeiger X 256 mögliche Befehle), wenn es sich um ein Array mit geringer Dichte handelt. Stattdessen habe ich eine Tabelle mit 8-Bit-Offsets für jeden möglichen Befehl. Dieser Offset wird dann verwendet, um in meine eigentliche Sprungtabelle zu indizieren, die jetzt nur noch NUM_COMMANDS * sizeof(pointer) sein muss. In meinem Fall mit 10 definierten Befehlen. Meine Sprungtabelle ist 20 Bytes lang und ich habe eine Offset-Tabelle, die 256 Bytes lang ist, was insgesamt 276 Bytes statt 512 Bytes entspricht. Ich rufe dann meine Funktionen so auf:
jump_table[offset_table[command]]();
Anstatt von
jump_table[command]();
Ich kann eine Offset-Tabelle wie folgt erstellen:
/* initialize every offset to 0 */
static uint8_t offset_table[256] = {0};
/* for each valid command, initialize the corresponding offset */
#define ENTRY(a,b,c) offset_table[c] = offsetof(offset_struct_t, b);
COMMAND_TABLE
#undef ENTRY
wobei offsetof ein Standardbibliotheksmakro ist, das in “stddef.h” definiert ist
Als Nebeneffekt gibt es eine sehr einfache Möglichkeit festzustellen, ob ein Befehlscode unterstützt wird oder nicht:
bool command_is_valid(uint8_t command)
{
/* return false if not valid, or true (non 0) if valid */
return offset_table[command];
}
Aus diesem Grund habe ich in meiner COMMAND_TABLE auch das Befehlsbyte 0 reserviert. Ich kann eine Funktion namens “process_reserved()” erstellen, die aufgerufen wird, wenn ein ungültiges Befehlsbyte zum Indexieren in meine Offset-Tabelle verwendet wird.
-
Wow! Ich akzeptiere demütig diese überlegene Antwort. (Aber Sie sollten den „Benutzer-Makro“-Stil in Betracht ziehen: Sie müssen nichts aufheben, Sie müssen sich nicht den inneren „Variablen“-Namen merken.)
– Luser Drog
23. Februar 2012 um 6:07 Uhr
-
Vielen Dank, habe heute wieder was neues gelernt. Jetzt kann ich anstelle all meiner #define und #undef Folgendes tun: REGISTERTABLE(AS_DECLARATION) REGISTERTABLE(AS_INITIALIZER) Sehr cool!
– ACRL
24. Februar 2012 um 13:47 Uhr
-
“Im Ernst, wo warst du mein ganzes Leben x-Makros!” Sie lauern in der Hölle und warten höchstwahrscheinlich darauf, dass ein ahnungsloser Programmierer sie beschwört. In modernem C können Sie eine direkte, enge Kopplung zwischen der Sprungtabelle und den Enums wie folgt erstellen:
p_func_t jumptable[] = { [STATE0] = func0, [STATE1] = func1 };
. Beachten Sie das[]
für die Arraygröße. Um nun sicherzustellen, dass kein Element fehlt, fügen Sie eine Prüfung zur Kompilierzeit hinzu:_Static_assert(NUM_STATES == sizeof jumptable/sizeof *jumptable, "error");
. Tippsicher, lesbar, kein einziges Makro in Sicht.– Ludin
6. Oktober 2016 um 7:50 Uhr
-
Mein Punkt hier ist, dass x Makros sein sollten der allerletzte Auswegund nicht das erste, was einem in den Sinn kommt, wenn man mit einem Programmdesignproblem konfrontiert ist.
– Ludin
6. Oktober 2016 um 7:57 Uhr
-
Luser Drog
X-Makros sind im Wesentlichen parametrisierte Vorlagen. Sie sind also das richtige Werkzeug für den Job, wenn Sie mehrere ähnliche Dinge in mehreren Ausführungen benötigen. Sie ermöglichen es Ihnen, ein abstraktes Formular zu erstellen und es nach verschiedenen Regeln zu instanziieren.
Ich verwende X-Makros, um Enum-Werte als Strings auszugeben. Und seit ich darauf gestoßen bin, bevorzuge ich diese Form, die ein “Benutzer” -Makro benötigt, um es auf jedes Element anzuwenden. Das Einschließen mehrerer Dateien ist einfach viel schmerzhafter, damit zu arbeiten.
/* x-macro constructors for error and type
enums and string tables */
#define AS_BARE(a) a ,
#define AS_STR(a) #a ,
#define ERRORS(_) \
_(noerror) \
_(dictfull) _(dictstackoverflow) _(dictstackunderflow) \
_(execstackoverflow) _(execstackunderflow) _(limitcheck) \
_(VMerror)
enum err { ERRORS(AS_BARE) };
char *errorname[] = { ERRORS(AS_STR) };
/* puts(errorname[(enum err)limitcheck]); */
Ich verwende sie auch für den Funktionsversand basierend auf dem Objekttyp. Wiederum durch Hijacking desselben Makros, mit dem ich die Enum-Werte erstellt habe.
#define TYPES(_) \
_(invalid) \
_(null) \
_(mark) \
_(integer) \
_(real) \
_(array) \
_(dict) \
_(save) \
_(name) \
_(string) \
/*enddef TYPES */
#define AS_TYPE(_) _ ## type ,
enum { TYPES(AS_TYPE) };
Die Verwendung des Makros garantiert, dass alle meine Array-Indizes mit den zugehörigen Aufzählungswerten übereinstimmen, da sie ihre verschiedenen Formen mit den bloßen Token aus der Makrodefinition (dem TYPES-Makro) erstellen.
typedef void evalfunc(context *ctx);
void evalquit(context *ctx) { ++ctx->quit; }
void evalpop(context *ctx) { (void)pop(ctx->lo, adrent(ctx->lo, OS)); }
void evalpush(context *ctx) {
push(ctx->lo, adrent(ctx->lo, OS),
pop(ctx->lo, adrent(ctx->lo, ES)));
}
evalfunc *evalinvalid = evalquit;
evalfunc *evalmark = evalpop;
evalfunc *evalnull = evalpop;
evalfunc *evalinteger = evalpush;
evalfunc *evalreal = evalpush;
evalfunc *evalsave = evalpush;
evalfunc *evaldict = evalpush;
evalfunc *evalstring = evalpush;
evalfunc *evalname = evalpush;
evalfunc *evaltype[stringtype/*last type in enum*/+1];
#define AS_EVALINIT(_) evaltype[_ ## type] = eval ## _ ;
void initevaltype(void) {
TYPES(AS_EVALINIT)
}
void eval(context *ctx) {
unsigned ades = adrent(ctx->lo, ES);
object t = top(ctx->lo, ades, 0);
if ( isx
evaltype[type
else
evalpush(ctx);
}
Die Verwendung von X-Makros auf diese Weise hilft dem Compiler tatsächlich dabei, hilfreiche Fehlermeldungen auszugeben. Ich habe die Evalarray-Funktion oben weggelassen, weil sie von meinem Punkt ablenken würde. Aber wenn Sie versuchen, den obigen Code zu kompilieren (indem Sie die anderen Funktionsaufrufe auskommentieren und natürlich eine Dummy-Typedef für den Kontext bereitstellen), würde sich der Compiler über eine fehlende Funktion beschweren. Für jeden neuen Typ, den ich hinzufüge, werde ich daran erinnert, einen Handler hinzuzufügen, wenn ich dieses Modul neu kompiliere. So trägt das X-Makro dazu bei, dass parallele Strukturen auch bei wachsendem Projekt erhalten bleiben.
Bearbeiten:
Diese Antwort hat meinen Ruf um 50 % erhöht. Also hier noch ein bisschen. Das Folgende ist ein negatives BeispielBeantwortung der Frage: Wenn nicht X-Makros verwenden?
Dieses Beispiel zeigt das Packen beliebiger Codefragmente in den X-“Datensatz”. Ich habe diesen Zweig des Projekts schließlich aufgegeben und diese Strategie in späteren Entwürfen nicht mehr verwendet (und nicht aus Mangel an Versuchen). Irgendwie wurde es unschön. Tatsächlich heißt das Makro X6, weil es an einer Stelle 6 Argumente gab, aber ich hatte es satt, den Makronamen zu ändern.
/* Object types */
/* "'X'" macros for Object type definitions, declarations and initializers */
// a b c d
// enum, string, union member, printf d
#define OBJECT_TYPES \
X6( nulltype, "null", int dummy , ("<null>")) \
X6( marktype, "mark", int dummy2 , ("<mark>")) \
X6( integertype, "integer", int i, ("%d",o.i)) \
X6( booleantype, "boolean", bool b, (o.b?"true":"false")) \
X6( realtype, "real", float f, ("%f",o.f)) \
X6( nametype, "name", int n, ("%s%s", \
(o.flags & Fxflag)?"":"https://stackoverflow.com/", names[o.n])) \
X6( stringtype, "string", char *s, ("%s",o.s)) \
X6( filetype, "file", FILE *file, ("<file %p>",(void *)o.file)) \
X6( arraytype, "array", Object *a, ("<array %u>",o.length)) \
X6( dicttype, "dict", struct s_pair *d, ("<dict %u>",o.length)) \
X6(operatortype, "operator", void (*o)(), ("<op>")) \
#define X6(a, b, c, d) #a,
char *typestring[] = { OBJECT_TYPES };
#undef X6
// the Object type
//forward reference so s_object can contain s_objects
typedef struct s_object Object;
// the s_object structure:
// a bit convoluted, but it boils down to four members:
// type, flags, length, and payload (union of type-specific data)
// the first named union member is integer, so a simple literal object
// can be created on the fly:
// Object o = {integertype,0,0,4028}; //create an int object, value: 4028
// Object nl = {nulltype,0,0,0};
struct s_object {
#define X6(a, b, c, d) a,
enum e_type { OBJECT_TYPES } type;
#undef X6
unsigned int flags;
#define Fread 1
#define Fwrite 2
#define Fexec 4
#define Fxflag 8
size_t length; //for lint, was: unsigned int
#define X6(a, b, c, d) c;
union { OBJECT_TYPES };
#undef X6
};
Ein großes Problem waren die Zeichenfolgen im printf-Format. Es sieht zwar cool aus, ist aber nur Hokuspokus. Da es nur in einer Funktion verwendet wird, trennt die übermäßige Verwendung des Makros tatsächlich Informationen, die zusammen sein sollten; und es macht die Funktion von selbst unlesbar. Die Verschleierung ist bei einer Debugging-Funktion wie dieser doppelt unglücklich.
//print the object using the type's format specifier from the macro
//used by O_equal (ps: =) and O_equalequal (ps: ==)
void printobject(Object o) {
switch (o.type) {
#define X6(a, b, c, d) \
case a: printf d; break;
OBJECT_TYPES
#undef X6
}
}
Also lass dich nicht mitreißen. Wie ich es getan habe.
-
Ich habe mir ein paar verschiedene Bibliotheken angesehen, um mit “Objekten” in C umzugehen – wie Cello und GObject, aber beide haben es für meinen Geschmack ein bisschen weit gebracht. Dieser Beitrag und Ihr Github-Code andererseits – tolle Sachen, danke für die inspiration. 🙂
– Christoffer Bubach
30. Juli 2020 um 2:24 Uhr
-
Das ist sehr schön zu hören. Ich habe diese auch studiert und mir das Lisp 1.1-Handbuch angesehen. Die neuesten Objekte, die ich erstellt habe, sind für Parser-Kombinatoren. Ich habe die GC wirklich klein und einfach dort bekommen. Lass mich unbedingt wissen, was du baust. Diese Art von Sachen scheint immer etwas Cooles zu ergeben. 🙂
– Luser Drog
30. Juli 2020 um 8:11 Uhr
Roland Illig
Einige reale Anwendungen von X-Makros durch beliebte und große Projekte:
Java-HotSpot
In der Oracle HotSpot Virtual Machine für die Programmiersprache Java® befindet sich die Datei globals.hpp
die die verwendet RUNTIME_FLAGS
auf diese Art.
Siehe Quellcode:
Chrom
Das Liste der Netzwerkfehler in net_error_list.h ist eine lange, lange Liste von Makroerweiterungen dieser Form:
NET_ERROR(IO_PENDING, -1)
Es wird von verwendet net_errors.h aus demselben Verzeichnis:
enum Error {
OK = 0,
#define NET_ERROR(label, value) ERR_ ## label = value,
#include "net/base/net_error_list.h"
#undef NET_ERROR
};
Das Ergebnis dieser Präprozessormagie ist:
enum Error {
OK = 0,
ERR_IO_PENDING = -1,
};
Was mir an dieser speziellen Verwendung nicht gefällt, ist, dass der Name der Konstante dynamisch durch Hinzufügen von erstellt wird ERR_
. In diesem Beispiel NET_ERROR(IO_PENDING, -100)
definiert die Konstante ERR_IO_PENDING
.
Mit einer einfachen Textsuche nach ERR_IO_PENDING
, ist es nicht möglich zu sehen, wo diese Konstante definiert wurde. Um die Definition zu finden, muss man stattdessen suchen IO_PENDING
. Dies erschwert die Navigation im Code und trägt daher zur Verschleierung der gesamten Codebasis.
-
Ref: hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/…
– Kevinarpe
12. Dezember 2015 um 13:26 Uhr
-
Könnten Sie etwas von diesem Code einfügen? Dies ist praktisch eine Nur-Link-Antwort, wie sie derzeit vorliegt.
– TankorSmash
1. November 2017 um 0:27 Uhr
Ich verwende gerne X-Makros zum Erstellen von “Rich Enumerations”, die das Iterieren der Enum-Werte sowie das Abrufen der Zeichenfolgendarstellung für jeden Enum-Wert unterstützen:
#define MOUSE_BUTTONS \
X(LeftButton, 1) \
X(MiddleButton, 2) \
X(RightButton, 4)
struct MouseButton {
enum Value {
None = 0
#define X(name, value) ,name = value
MOUSE_BUTTONS
#undef X
};
static const int *values() {
static const int a[] = {
None,
#define X(name, value) name,
MOUSE_BUTTONS
#undef X
-1
};
return a;
}
static const char *valueAsString( Value v ) {
#define X(name, value) static const char str_##name[] = #name;
MOUSE_BUTTONS
#undef X
switch ( v ) {
case None: return "None";
#define X(name, value) case name: return str_##name;
MOUSE_BUTTONS
#undef X
}
return 0;
}
};
Dies definiert nicht nur a MouseButton::Value
enum, es lässt mich auch Dinge tun wie
// Print names of all supported mouse buttons
for ( const int *mb = MouseButton::values(); *mb != -1; ++mb ) {
std::cout << MouseButton::valueAsString( (MouseButton::Value)*mb ) << "\n";
}
Ich verwende ein ziemlich umfangreiches X-Makro, um den Inhalt einer INI-Datei in eine Konfigurationsstruktur zu laden, die sich unter anderem um diese Struktur dreht.
So sieht meine “configuration.def”-Datei aus:
#define NMB_DUMMY(...) X(__VA_ARGS__)
#define NMB_INT_DEFS \
TEXT("long int") , long , , , GetLongValue , _ttol , NMB_SECT , SetLongValue ,
#define NMB_STR_DEFS NMB_STR_DEFS__(TEXT("string"))
#define NMB_PATH_DEFS NMB_STR_DEFS__(TEXT("path"))
#define NMB_STR_DEFS__(ATYPE) \
ATYPE , basic_string<TCHAR>* , new basic_string<TCHAR>\
, delete , GetValue , , NMB_SECT , SetValue , *
/* X-macro starts here */
#define NMB_SECT "server"
NMB_DUMMY(ip,TEXT("Slave IP."),TEXT("10.11.180.102"),NMB_STR_DEFS)
NMB_DUMMY(port,TEXT("Slave portti."),TEXT("502"),NMB_STR_DEFS)
NMB_DUMMY(slaveid,TEXT("Slave protocol ID."),0xff,NMB_INT_DEFS)
.
. /* And so on for about 40 items. */
Es ist etwas verwirrend, gebe ich zu. Schnell wurde klar, dass ich eigentlich nicht nach jedem Feldmakro all diese Typdeklarationen schreiben möchte. (Keine Sorge, es gibt einen großen Kommentar, um alles zu erklären, was ich der Kürze halber weggelassen habe.)
Und so deklariere ich die Konfigurationsstruktur:
typedef struct {
#define X(ID,DESC,DEFVAL,ATYPE,TYPE,...) TYPE ID;
#include "configuration.def"
#undef X
basic_string<TCHAR>* ini_path; //Where all the other stuff gets read.
long verbosity; //Used only by console writing functions.
} Config;
Dann werden im Code zunächst die Default-Werte in die Konfigurationsstruktur eingelesen:
#define X(ID,DESC,DEFVAL,ATYPE,TYPE,CONSTRUCTOR,DESTRUCTOR,GETTER,STRCONV,SECT,SETTER,...) \
conf->ID = CONSTRUCTOR(DEFVAL);
#include "configuration.def"
#undef X
Anschließend wird die INI mithilfe der Bibliothek SimpleIni wie folgt in die Konfigurationsstruktur eingelesen:
#define X(ID,DESC,DEFVAL,ATYPE,TYPE,CONSTRUCTOR,DESTRUCTOR,GETTER,STRCONV,SECT,SETTER,DEREF...)\
DESTRUCTOR (conf->ID);\
conf->ID = CONSTRUCTOR( ini.GETTER(TEXT(SECT),TEXT(#ID),DEFVAL,FALSE) );\
LOG3A(<< left << setw(13) << TEXT(#ID) << TEXT(": ") << left << setw(30)\
<< DEREF conf->ID << TEXT(" (") << DEFVAL << TEXT(").") );
#include "configuration.def"
#undef X
Und Überschreibungen von Befehlszeilen-Flags, die ebenfalls mit denselben Namen (in GNU-Langform) formatiert sind, werden wie folgt in der folgenden Weise unter Verwendung der Bibliothek SimpleOpt angewendet:
enum optflags {
#define X(ID,...) ID,
#include "configuration.def"
#undef X
};
CSimpleOpt::SOption sopt[] = {
#define X(ID,DESC,DEFVAL,ATYPE,TYPE,...) {ID,TEXT("--") #ID TEXT("="), SO_REQ_CMB},
#include "configuration.def"
#undef X
SO_END_OF_OPTIONS
};
CSimpleOpt ops(argc,argv,sopt,SO_O_NOERR);
while(ops.Next()){
switch(ops.OptionId()){
#define X(ID,DESC,DEFVAL,ATYPE,TYPE,CONSTRUCTOR,DESTRUCTOR,GETTER,STRCONV,SECT,...) \
case ID:\
DESTRUCTOR (conf->ID);\
conf->ID = STRCONV( CONSTRUCTOR ( ops.OptionArg() ) );\
LOG3A(<< TEXT("Omitted ")<<left<<setw(13)<<TEXT(#ID)<<TEXT(" : ")<<conf->ID<<TEXT(" ."));\
break;
#include "configuration.def"
#undef X
}
}
Und so weiter, ich verwende dasselbe Makro auch, um die Ausgabe von –help -flag und die Beispiel-Standard-INI-Datei zu drucken, configuration.def ist 8 Mal in meinem Programm enthalten. “Viereckiger Stift in ein rundes Loch”, vielleicht; Wie würde ein wirklich kompetenter Programmierer damit umgehen? Viele, viele Loops und String-Verarbeitung?
Peter Mortensen
https://github.com/whunmr/DataEx
Ich verwende die folgenden X-Makros, um eine C++-Klasse mit integrierter Serialisierungs- und Deserialisierungsfunktion zu generieren.
#define __FIELDS_OF_DataWithNested(_) \
_(1, a, int ) \
_(2, x, DataX) \
_(3, b, int ) \
_(4, c, char ) \
_(5, d, __array(char, 3)) \
_(6, e, string) \
_(7, f, bool)
DEF_DATA(DataWithNested);
Verwendungszweck:
TEST_F(t, DataWithNested_should_able_to_encode_struct_with_nested_struct) {
DataWithNested xn;
xn.a = 0xCAFEBABE;
xn.x.a = 0x12345678;
xn.x.b = 0x11223344;
xn.b = 0xDEADBEEF;
xn.c = 0x45;
memcpy(&xn.d, "XYZ", strlen("XYZ"));
char buf_with_zero[] = {0x11, 0x22, 0x00, 0x00, 0x33};
xn.e = string(buf_with_zero, sizeof(buf_with_zero));
xn.f = true;
__encode(DataWithNested, xn, buf_);
char expected[] = { 0x01, 0x04, 0x00, 0xBE, 0xBA, 0xFE, 0xCA,
0x02, 0x0E, 0x00 /*T and L of nested X*/,
0x01, 0x04, 0x00, 0x78, 0x56, 0x34, 0x12,
0x02, 0x04, 0x00, 0x44, 0x33, 0x22, 0x11,
0x03, 0x04, 0x00, 0xEF, 0xBE, 0xAD, 0xDE,
0x04, 0x01, 0x00, 0x45,
0x05, 0x03, 0x00, 'X', 'Y', 'Z',
0x06, 0x05, 0x00, 0x11, 0x22, 0x00, 0x00, 0x33,
0x07, 0x01, 0x00, 0x01};
EXPECT_TRUE(ArraysMatch(expected, buf_));
}
Außerdem ist ein weiteres Beispiel dabei https://github.com/whunmr/msgrpc.
nyanpasu64
Chromium bietet eine interessante Variante eines X-Makros an dom_code_data.inc. Nur ist es nicht nur ein Makro, sondern eine völlig separate Datei. Diese Datei ist für die Zuordnung von Tastatureingaben zwischen Scancodes, USB-HID-Codes und zeichenfolgenähnlichen Namen verschiedener Plattformen vorgesehen.
Die Datei enthält Code wie:
DOM_CODE_DECLARATION {
// USB evdev XKB Win Mac Code
DOM_CODE(0x000000, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NONE), // Invalid
...
};
Jeder Makroaufruf übergibt tatsächlich 7 Argumente, und das Makro kann auswählen, welche Argumente verwendet und welche ignoriert werden sollen. Eine Verwendung ist die Zuordnung zwischen OS-Keycodes und plattformunabhängigen Scancodes und DOM-Strings. Auf verschiedenen Betriebssystemen werden verschiedene Makros verwendet, um die für dieses Betriebssystem geeigneten Schlüsselcodes auszuwählen.
// Table of USB codes (equivalent to DomCode values), native scan codes,
// and DOM Level 3 |code| strings.
#if defined(OS_WIN)
#define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
{ usb, win, code }
#elif defined(OS_LINUX)
#define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
{ usb, xkb, code }
#elif defined(OS_MACOSX)
#define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
{ usb, mac, code }
#elif defined(OS_ANDROID)
#define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
{ usb, evdev, code }
#else
#define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
{ usb, 0, code }
#endif
#define DOM_CODE_DECLARATION const KeycodeMapEntry usb_keycode_map[] =
#include "ui/events/keycodes/dom/dom_code_data.inc"
#undef DOM_CODE
#undef DOM_CODE_DECLARATION
Ein Eingebettet.com aktuelle Serie: Teil 1, Teil 2, Teil 3 Zu diesem Thema. Antworten hier sind ebenso lehrreich.
– ungekünstelter Lärm
30. März 2013 um 19:14 Uhr
Für diejenigen, die über die Verwendung von X-Makros nachdenken, sollten Sie das folgende Format verwenden:
(() () ())
Anstatt von:( , , )
. Dies macht sie nützlicher für rekursive, variadische Makrosituationen. Weitere Informationen zum Warum/Wie finden Sie hier: stackoverflow.com/a/66130832/1599699 Außerdem können Sie die Verwendung dieser hässlichen \ in Ihren Makros vermeiden, indem Sie einfach jeden Eintrag in eine eigene Zeile einfügen und die Datei einfügen. siehe hier für mehr: quuxplusone.github.io/blog/2021/02/01/x-macros– Andreas
10. Februar 2021 um 4:25 Uhr