[解決済み] X-Macrosの実戦的な活用法
質問
今知ったのですが X-Macros . X-Macrosの実際の使用例を教えてください。どのような場合にX-Macrosが最適なのでしょうか?
どのように解決するのですか?
私が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-macros はどこにいたのでしょうか!
それで今、私は次のような表を定義しています。
#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
しかし、私のお気に入りの使い方は、通信ハンドラに関して言えば
まず、各コマンド名とコードを含むcommsテーブルを作成します。
#define COMMAND_TABLE \
ENTRY(RESERVED, reserved, 0x00) \
ENTRY(COMMAND1, command1, 0x01) \
ENTRY(COMMAND2, command2, 0x02) \
...
ENTRY(COMMANDX, commandX, 0x0X) \
大文字と小文字の両方の名前をテーブルに入れていますが、これは大文字が列挙型、小文字が関数名に使われるからです。
それから、各コマンドがどのようなものかを定義するために、各コマンドに構造体を定義しています。
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
};
応答長の列挙を定義できる
enum
{
#define ENTRY(a,b,c) a##_RESP_LENGTH = sizeof(b##_resp_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をインスタンス化することはなく、コンパイラが私のコマンド数の定義を生成するための方法として使用しているだけです。
次に、私は次のように関数ポインタのテーブルを生成することができます。
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 バイト X 256 コマンド) を使用したくありません。 その代わりに、可能なコマンドごとに8ビットオフセットのテーブルを用意することにします。 このオフセットは、実際のジャンプテーブルのインデックスとして使用され、NUM_COMMANDS * sizeof(pointer)である必要があります。 私の場合、10個のコマンドを定義しています。 ジャンプテーブルの長さは20バイト、オフセットテーブルの長さは256バイトで、合計で512バイトではなく276バイトになります。 そして、このように関数を呼び出します。
jump_table[offset_table[command]]();
の代わりに
jump_table[command]();
こんな感じでオフセットテーブルが作れます。
/* 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
ここで、offsetof は "stddef.h" で定義されている標準ライブラリマクロです。
副次的な利点として、あるコマンドコードがサポートされているかどうかを判断する非常に簡単な方法があります。
bool command_is_valid(uint8_t command)
{
/* return false if not valid, or true (non 0) if valid */
return offset_table[command];
}
COMMAND_TABLEでコマンドバイト0を予約したのもこのためです。 "process_reserved()" という関数を一つ作っておけば、無効なコマンドバイトがオフセットテーブルのインデックスに使われたときに呼び出されます。
関連
-
[解決済み] C関数から文字列を返す
-
[解決済み] SQLiteのINSERT/per-secondのパフォーマンスを向上させる
-
[解決済み] C言語では「?」演算子は何をするのですか?
-
[解決済み] マクロで無意味なdo-while文やif-else文を使っているのはなぜですか?
-
[解決済み] C言語のi++と++iに性能差はあるのでしょうか?
-
[解決済み] LD_PRELOADのトリックとは何ですか?
-
[解決済み】高放射能環境下で使用するアプリケーションのコンパイルについて
-
[解決済み】ストリクト・エイリアシング・ルールとは何ですか?
-
[解決済み】Visual Studio 2012/2013/2015/2017/2019でマクロの記録/再生は可能ですか?
-
[解決済み] あなたが今まで遭遇した最悪の実戦的マクロ/プリプロセッサの乱用は何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
解決済み] g++ コンパイルエラー: ')'トークンの前に一次式があることが予想される
-
Cエラー [エラー] 代入_Ashesの左オペランドにlvalueが必要です-プログラマーズ・シークレット
-
C: 1を求める! + 2! + 3! + ... + n! (ループ)
-
[解決済み] mallocで文字列を確保する
-
[解決済み] C言語の**はどういう意味ですか?
-
[解決済み] C関数から文字列を返す
-
[解決済み] C - Setデータ構造を実装するには?
-
[解決済み] プログラム終了前にmallocの後にfreeをしないと本当に何が起こるのか?
-
[解決済み] C言語の構造体(CGRectやCGPointなど)をNSLog化することは可能ですか?
-
[解決済み] C 言語の構造体におけるデフォルト値