C 言語 マクロ講座 # ## 混合編 ― 2007年03月21日 11時12分41秒
C でメッセージプロセスをやると、最初の数バイトが、メッセージ ID で、メッセージ ID を元に続くものを解析することはよ くある操作だ。
今回は、ID が 0 の時は、int 型と解釈するべき文字列で、1 の時は int 型がそのままのバイナリ形式で、2 の時は、float 型と解釈するべき文字列が、3 の時は float 型として扱うバイナリ形式が埋め込まれているとする。
それらの入力を、目的の型に適切に変換して、一時保存をする。そして、一時保存をしたものを printf を使って表示する例が以下のプログラムだ。
transaction_table を構築しやすいように、わざとメッセージ ID を割り当てたのだ。もし、仕様の製作などにこの様な融通を効かせる事ができる場合は、既にこの段階で、プログラムは始まっているのである。このメッセージ ID の割り当て方法を利用して、コードをマクロで自動生成できる様にしてある。
% cat multi.c
#ifndef LIB
#include
#endif
#define FUNCTIONS(type, opr) \
void scan_ ## type(void *input, void *output) \
{ \
printf("%s %s", __func__, *(char **)input); \
sscanf(*(char**)input, "%" #type, output); \
} \
void assign_ ## type(void *input, void *output) \
{ \
printf("%s %" #opr, __func__, *(type*)input); \
*(type *)output = *(type *)input; \
} \
void print_ ## type(void *addr) \
{ \
printf(" %s:%" #opr, __func__, *(type *) addr); \
}
FUNCTIONS(int, d);
FUNCTIONS(float, f);
#define TYPE(id, type) \
{ id, #type, scan_ ## type, print_ ## type}, \
{ id + 1, #type, assign_ ## type, print_ ## type}
struct
{
int id;
char *name;
void (*read)(void *input, void *output);
void (*print)(void *addr);
} transaction_table[] =
{
TYPE(0, int),
TYPE(2, float),
};
struct msg
{
int id;
union
{
char *string;
int integer;
float floating;
} content;
};
void process(struct msg *message)
{
char buffer[32];
if(message->id < 0 ||
message->id >= (sizeof(transaction_table)/sizeof(transaction_table[0])))
return;
transaction_table[message->id].read(&message->content, buffer);
transaction_table[message->id].print(buffer);
}
int main()
{
struct msg msgs[] =
{
{ 0, },
{ 1, },
{ 2, },
{ 3, },
};
int i;
msgs[0].content.string = "11";
msgs[1].content.integer = 22;
msgs[2].content.string = "33.33";
msgs[3].content.floating = 44.44;
for(i = 0; i < sizeof(msgs)/sizeof(msgs[0]); i++)
{
printf("id = %d:", msgs[i].id);
process(&msgs[i]);
printf("\n");
}
}
実行結果はこうなる。書いた行数の割りには出力は少ない。
% make multi
cc -O2 -fno-strict-aliasing -pipe multi.c -o multi
% ./multi
id = 0:scan_int 11 print_int:11
id = 1:assign_int 22 print_int:22
id = 2:scan_float 33.33 print_float:33.330002
id = 3:assign_float 44.439999 print_float:44.439999
プリプロセッサを通すとこうなる。関数や、配列の項目が、マクロで次々と構成されている。
# 1 "multi.c"
# 1 ""
# 1 ""
# 1 "multi.c"
# 21 "multi.c"
void scan_int(void *input, void *output) { printf("%s %s", __func__, *(char **)i
nput); sscanf(*(char**)input, "%" "int", output); } void assign_int(void *input,
void *output) { printf("%s %" "d", __func__, *(int*)input); *(int *)output = *(
int *)input; } void print_int(void *addr) { printf(" %s:%" "d", __func__, *(int
*) addr); };
void scan_float(void *input, void *output) { printf("%s %s", __func__, *(char **
)input); sscanf(*(char**)input, "%" "float", output); } void assign_float(void *
input, void *output) { printf("%s %" "f", __func__, *(float*)input); *(float *)o
utput = *(float *)input; } void print_float(void *addr) { printf(" %s:%" "f", __
func__, *(float *) addr); };
struct
{
int id;
char *name;
void (*read)(void *input, void *output);
void (*print)(void *addr);
} transaction_table[] =
{
{ 0, "int", scan_int, print_int}, { 0 + 1, "int", assign_int, print_int},
{ 2, "float", scan_float, print_float}, { 2 + 1, "float", assign_float, prin
t_float},
};
struct msg
{
int id;
union
{
char *string;
int integer;
float floating;
} content;
};
void process(struct msg *messag)
{
char buffer[32];
if(message->id < 0 ||
message->id >= (sizeof(transaction_table)/sizeof(transaction_table[0])))
return;
transaction_table[message->id].read(&message->content, buffer);
transaction_table[message->id].print(buffer);
}
int main()
{
struct msg msgs[] =
{
{ 0, },
{ 1, },
{ 2, },
{ 3, },
};
int i;
msgs[0].content.string = "11";
msgs[1].content.integer = 22;
msgs[2].content.string = "33.33";
msgs[3].content.floating = 44.44;
for(i = 0; i < sizeof(msgs)/sizeof(msgs[0]); i++)
{
printf("id = %d:", msgs[i].id);
process(&msgs[i]);
printf("\n");
}
}
例として動かすための初期設定がずいぶん長くなっている。
ここまで、マクロで処理してしまうと、 読み辛いなどの意見も出てくると思われる。しかし、もし新たに同じような形で、long int 型や double 型も扱えるようにするとなると、マクロを使っていない場合は、かなりの大仕事になる。マクロを多用しているので、long int と double 用に各二行ずつ、計四行行足すとそれで終わりだ。可読性と拡張性を天秤に掛けることになる。
実は、今回のプログラムは、sscanf で "%l" を取る long 型、つまり long int 型や double 型ではなぜか、 print_ ## type がうまく動作しなかった。今回はただの例なので、原因追及の深追いはしていない。
この様な時に、マクロを多用するとデバッグが難しくなるから、マクロの関数は使わないという人たちも多い。実際に、 multi.c をデバッガで一行ずつ実行すると、同じ所で足踏みをしているようにも見える。これは、ただの創造性の欠如だ。直接コンパイルしたものがデバッガで扱いにくいのなら、プリプロセスをかけたファイルをコンパイルすればいいだけだ。何も目の前に水溜があるとて、足を踏み入れる必要はない。一歩横に足をそらせばいいだけだ。
最近のコメント