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 をデバッガで一行ずつ実行すると、同じ所で足踏みをしているようにも見える。これは、ただの創造性の欠如だ。直接コンパイルしたものがデバッガで扱いにくいのなら、プリプロセスをかけたファイルをコンパイルすればいいだけだ。何も目の前に水溜があるとて、足を踏み入れる必要はない。一歩横に足をそらせばいいだけだ。

前回次回

コメント

_ あき♪ ― 2011年05月08日 12時04分59秒

誤ってるよ^^
@@ -6,7 +6,7 @@
   void scan_ ## type(void *input, void *output) \
   { \
     printf("%s %s", __func__, *(char**)input); \
-    sscanf(*(char**)input, "%" #type, output); \
+    sscanf(*(char**)input, "%" #opr, (type*)output); \
   } \
   void assign_ ## type(void *input, void *output) \
   { \

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※なお、送られたコメントはブログの管理者が確認するまで公開されません。

名前:
メールアドレス:
URL:
コメント:

トラックバック

このエントリのトラックバックURL: http://uyota.asablo.jp/blog/2007/03/21/1330815/tb

※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。