Reale Verwendung von X-Makros

Lesezeit: 18 Minuten

Benutzeravatar von Agnius Vasiliauskas
Agnius Wassilauskas

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?

  • 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


Benutzeravatar von ACRL
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

  • embedded.com/design/programmiersprachen-und-tools/4403953/…

    – AlphaGoku

    23. März 2018 um 2:36 Uhr

Benutzeravatar von luser droog
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

Benutzeravatar von Roland Illig
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.hppdie 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.

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?

Benutzeravatar von Peter Mortensen
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.

Benutzeravatar von nyanpasu64
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

1418930cookie-checkReale Verwendung von X-Makros

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy