2011-07-09 10 views
56

私はちょうどX-Macrosのことを知りました。実際にX-Macrosを使って見たことはありますか?彼らはいつ仕事のための正しいツールですか?X-Macrosの実際の使用

+3

アン* Embedded.com *最近直列である:その1](HTTP://www.embedded。X-macro-C-language-coding-errors-Part-1)、[Part 2](http://www.embedded.com/design/プログラミング言語とツール/ 4405283/Reduce-C - Xマクロによる言語コーディングエラー---第2部)、[第3部](http://www.embedded.com/design/programming-languages-and-tools/4408127/Reduce-C-language-coding-errors-X-macros-Part-3)を参照してください。ここの答えは有益なものです。 –

答えて

68

私は自分のコードで関数ポインタを使い始めた数年前にXマクロを発見しました。私は組み込みプログラマーであり、私は頻繁にステートマシンを使用します。多くの場合、私はこのようなコードを記述します。

/* 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}; 

問題は、私はそれは状態の私の列挙の順序と一致したことを、このような私の関数ポインタテーブルの順序を維持するために持っている、それは非常にエラーが発生しやすいと考えられていることでした。

私の友人は私をX-macrosに紹介しました。それはライトバルブが私の頭の中で消えたようでした。真剣に、あなたはどこで私の人生のxマクロになりましたか?

は、だから今、私は次のテーブル定義:

#define STATE_TABLE \ 
     ENTRY(STATE0, func0) \ 
     ENTRY(STATE1, func1) \ 
     ENTRY(STATE2, func2) \ 
     ... 
     ENTRY(STATEX, funcX) \ 

を、次のように私はそれを使用することができますボーナスとして

enum 
{ 
#define ENTRY(a,b) a, 
    STATE_TABLE 
#undef ENTRY 
    NUM_STATES 
}; 

p_func_t jumptable[NUM_STATES] = 
{ 
#define ENTRY(a,b) b, 
    STATE_TABLE 
#undef ENTRY 
}; 

を、私も持つことができますプリプロセッサは次のように関数プロトタイプを構築します:

#define ENTRY(a,b) static void b(void); 
    STATE_TABLE 
#undef ENTRY 

別の使用量は

#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 

レジスタを宣言して初期化するために、私のお気に入りの使用量はしかし、私は、各コマンド名とコードを含む、途切れのテーブルを作成し、それが通信ハンドラに来る

まずです:

#define COMMAND_TABLE \ 
    ENTRY(RESERVED, reserved, 0x00) \ 
    ENTRY(COMMAND1, command1, 0x01) \ 
    ENTRY(COMMAND2, command2, 0x02) \ 
    ... 
    ENTRY(COMMANDX, commandX, 0x0X) \ 

表に大文字と小文字の両方の名前があります。これは、大文字がenums a関数名には小文字を使います。

それから私はまた、それぞれの構造体は、各コマンドがどのように見えるかを定義するコマンドを定義します。同様に、私は、各コマンド応答のための構造体を定義

typedef struct {...}command1_cmd_t; 
typedef struct {...}command2_cmd_t; 

etc. 

typedef struct {...}command1_resp_t; 
typedef struct {...}command2_resp_t; 

etc. 

それから私は私の命令コードを定義することができます列挙型:

enum 
{ 
#define ENTRY(a,b,c) a##_CMD = c, 
    COMMAND_TABLE 
#undef ENTRY 
}; 

私のコマンド長の列挙を定義できます:

enum 
{ 
#define ENTRY(a,b,c) a##_CMD_LENGTH = sizeof(b##_cmd_t); 
    COMMAND_TABLE 
#undef ENTRY 
}; 

私は私の応答長列挙を定義することができます。

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) 

注:私は実際にoffset_struct_tをインスタンス化したことがない、私は

enum 
{ 
#define ENTRY(a,b,c) a##_RESP_LENGTH = sizeof(b##_resp_t); 
    COMMAND_TABLE 
#undef ENTRY 
}; 

私は次のようにありますどのように多くのコマンドを決定することができますコンパイラのために私のために生成するコマンドの定義のための方法としてそれを使用してください。

注意次のように私は関数ポインタの私のテーブルを生成することができます

p_func_t jump_table[NUMBER_OF_COMMANDS] = 
{ 
#define ENTRY(a,b,c) process_##b, 
    COMMAND_TABLE 
#undef ENTRY 
} 

そして、私の関数のプロトタイプを:

#define ENTRY(a,b,c) void process_##b(void); 
    COMMAND_TABLE 
#undef ENTRY 

さて、最後にこれまでクールな使用のために、私は、コンパイラの計算を持つことができます私の送信バッファがどれくらい大きくなければならないか。

/* 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 

再びこの組合は私のオフセット構造体のようなものです、それがインスタンス化されていない、代わりに私は自分の送信バッファサイズを宣言するためにsizeof演算子を使用することができます。

uint8_t tx_buf[sizeof(tx_buf_t)]; 

今私の送信バッファtx_bufは最適なサイズであると私は、この途切れハンドラにコマンドを追加すると、私のバッファは常に最適なサイズとなります。クール! メモリはしばしば組み込みシステムの制約であるため、ジャンプテーブルには512バイト(ポインタあたり2バイト、可能な256のコマンド)を使用したくありません。スパース配列。代わりに、私は各可能なコマンドの8ビットのオフセットのテーブルを持っています。このオフセットは、今ではNUM_COMMANDS * sizeof(ポインタ)である必要がある私の実際のジャンプテーブルにインデックスを付けるために使用されます。私の場合、10個のコマンドが定義されています。私のジャンプテーブルの長さは20バイトで、私は256バイトの長さのオフセットテーブルを持っています。これは512バイトの代わりに合計276バイトです。私はそのようにように私の関数を呼び出す:

jump_table[offset_table[command]](); 

代わりの

jump_table[command](); 

を、私はそうのようなオフセットテーブルを作成することができます。offsetofははで定義された標準ライブラリのマクロです

/* 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 

」 stddef。hは、」サイド利点として

は、コマンドコードがサポートされているかどうかを決定するために非常に簡単な方法があります:私のCOMMAND_TABLEに、私は私ができるコマンドバイト0を予約する理由

bool command_is_valid(uint8_t command) 
{ 
    /* return false if not valid, or true (non 0) if valid */ 
    return offset_table[command]; 
} 

これもあります無効なコマンドバイトを使用してオフセットテーブルにインデックスを付ける場合に呼び出される "process_reserved()"という関数を1つ作成します。

+0

うわー!私はこの優秀な答えに謙虚に受け入れます。 (しかし、あなたは "ユーザマクロ"スタイルを考慮する必要があります:何もundefする必要はなく、内側の "変数"名を覚える必要はありません) –

+0

@luserdroog "user-macro"スタイルはどういう意味ですか?私は#undefをCOMMAND_TABLE定義に置くことができたが、内側の "変数"名をどのように覚えておく必要がないかを知っている。私は私の場合、これが "入場"であると仮定します – ACRL

+0

私は私の答えに示された方法を意味します。内部名はダミーです。適用されるマクロ名はテンプレートマクロに渡されます。したがって、それぞれの再解釈には独自の記述名(AS_BAREまたはAS_STR)が付けられます。 –

5

Java®プログラミング言語用のOracle HotSpot仮想マシンには、というファイルがあり、その方法でRUNTIME_FLAGSを使用します。

ソースコードを参照してください:

+0

Ref:http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/runtime/globals.hpp – kevinarpe

+0

そのコードの一部を含めることができますか?現在のところ、これは事実上リンクオンリーの答えです。 – TankorSmash

29

X-マクロは基本的にパラメータ化テンプレートです。だから、いくつかの似たようなことがいくつか必要な場合は、彼らは仕事のための正しいツールです。それらを使用すると、抽象的なフォームを作成し、異なるルールに従ってインスタンス化することができます。

Xマクロを使用して列挙型の値を文字列として出力します。そしてそれに遭遇して以来、私は強くこのフォームを好みます。これは "user"マクロを各要素に適用します。複数のファイルを含むことは、作業するのがはるかに苦痛です。

/* 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]); */ 

私はまた、オブジェクトの種類に基づいて機能ディスパッチのためにそれらを使用しています。再び同じマクロをハイジャックして、私は列挙型の値を作成しました。それらはマクロ定義から裸のトークン(TYPESマクロ)を使用して、様々な形態を構築するためのすべての私の配列インデックスは、関連する列挙値と一致することを保証するマクロを使用

#define TYPES(_) \ 
    _(invalid) \ 
    _(null) \ 
    _(mark) \ 
    _(integer) \ 
    _(real) \ 
    _(array) \ 
    _(dict) \ 
    _(save) \ 
    _(name) \ 
    _(string) \ 
/*enddef TYPES */ 

#define AS_TYPE(_) _ ## type , 
enum { TYPES(AS_TYPE) }; 

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(t)) /* if executable */ 
     evaltype[type(t)](ctx); /* <--- the payoff is this line here! */ 
    else 
     evalpush(ctx); 
} 

このようにXマクロを使用すると、実際にコンパイラが役立つエラーメッセージを表示するのに役立ちます。私は上記からevalarray関数を省略しました。なぜなら、それは私の注意を逸らすからです。しかし、上記のコードをコンパイルしようとすると(もちろん、他の関数呼び出しをコメントアウトし、コンテキストにダミーのtypedefを指定すると)、コンパイラーは機能が不足していると不満を持ちます。私が追加する新しいタイプごとに、私はこのモジュールを再コンパイルするときにハンドラを追加することを思い出しています。したがって、Xマクロは、プロジェクトが成長しても並列構造が損なわれないことを保証するのに役立ちます。

編集:この答えは私の評判50%を調達している

。それではもう少しです。以下は、のネガティブ例ので、と答えたときではないをX-Macrosに使用していますか?

この例では、任意のコードフラグメントをXレコードにパックしています。私は最終的にプロジェクトのこのブランチを断念し、後のデザインでこの戦略を使用しませんでした。それはどういうわけか不安になった。実際、マクロはX6という名前になっています。なぜなら、ある時点で6つの引数があったからですが、マクロ名を変更するのは疲れました。

/* 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)?"":"/", 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 
}; 

大きな問題は、printf形式の文字列でした。それはクールに見えますが、それはちょうどホーカスフォーカスです。これは1つの関数内でしか使用されないため、マクロを過度に使用すると、一緒にすべき情報が実際に分離されます。それは機能をそれ自身で読むことができないようにする。難読化はこのようなデバッグ機能では二重に不幸です。

//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 
    } 
} 

だから逃げないでください。私のように

3

Xマクロを使用して列挙型の値を反復する 'リッチな列挙型'各列挙値の文字列表現を取得するだけでなく、

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

これだけではなくMouseButton::Value列挙型を定義し、それはまた、私は他のものの中で、コンフィギュレーション構造体にINIファイルの内容をロードするために、かなり大規模なX-マクロを使用

// Print names of all supported mouse buttons 
for (const int *mb = MouseButton::values(); *mb != -1; ++mb) { 
    std::cout << MouseButton::valueAsString((MouseButton::Value)*mb) << "\n"; 
} 
3

のようなものを行うことができますその構造体の周りを回転します。

これは私のように "configuration.def" -fileが見えるものです:それは少し混乱だ

#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. */ 

、私は認めます。私は実際にすべてのフィールドマクロの後にすべての型宣言を書く必要はないことがすぐに分かります。

を(私は簡潔にするため省略し、すべてを説明するための大きなコメントがあります、心配しないでください)そして、これは私が設定構造体を宣言する方法である:まず、コード、デフォルトでは、次に

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; 

を値は、コンフィギュレーション構造体に読み込まれる:

そして
#define X(ID,DESC,DEFVAL,ATYPE,TYPE,CONSTRUCTOR,DESTRUCTOR,GETTER,STRCONV,SECT,SETTER,...) \ 
    conf->ID = CONSTRUCTOR(DEFVAL); 
#include "configuration.def" 
#undef X 

次のように、INIは、ライブラリSimpleIniを使用して、コンフィギュレーション構造体に読み込まれる:

#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 
また、同じ名前(GNU長い形式で)でフォーマットされているコマンドラインフラグ、から

そしてオーバーライド、ある次のようにライブラリSimpleOptを使用してfoillowingの方法で適用されます。

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 
    } 
    } 

そしてそうで、私も使用--help -flagの出力とサンプルのデフォルトのiniファイルを出力する同じマクロ、configuration.defは私のプログラムに8回含まれています。 "丸い穴に四角いペグ"、おそらく;実際に有能なプログラマーがこれをどのように進めるでしょうか?たくさんのループと文字列処理?シリアル化して、C++クラスを生成し、functionlityの組み込みをデシリアライズするために、次のxmacrosを使用して

0

https://github.com/whunmr/DataEx

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

使用:

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

また、別の例ではhttps://github.com/whunmr/msgrpc

関連する問題