『Cプログラミング診断室』目次次(第6章 不慣れ ビットフィールド)

第6章 不慣れ

マクロ


■ダミー関数■

プログラムを書くとき、リスト6−1のように、中身のまったくないダミー関数を用意するでしょ う。ダミー関数は、その名のとおり何もしないのですが、一般には呼ばれたことだけは確認したい ので、「呼ばれたぞー」という類のメッセージを表示させることが多いです。

 
リスト6−1 ダミー関数

     1	win_bell()
     2	{
     3	        fprintf( stderr, "win_bell() is not supported\n" );
     4	}
     5	
     6	Notify_value    notify_next_event_func()
     7	{
     8	        fprintf( stderr, "notify_next_event_func() is not supported\n" );
     9	}
    10	
    11	pw_batch()
    12	{
    13	        fprintf( stderr, "pw_batch() is not supported\n" );
    14	}
    15	
    16	Event *canvas_window_event()
    17	{
    18	        fprintf( stderr, "canvas_window_event() is not supported\n" );
    19	}

ダミー関数は、とりあえず関数がないとリンクできず、デバッグを進められないときなどに必ず 必要になります。プログラムが大きくなると、全ての関数を用意してからテストということはなく、 開発の初期には、ダミー関数の山でしょう。だから、用意するダミー関数の数が何十個にもなるこ とは珍しくありません。そのようなとき、リスト1のように、関数名以外は全く同じ関数を繰り返 して書くことは退屈な、ばかばかしい作業です。

当然、マクロの出番です。マクロは、短い文字列の置換だけでなく、行末を\(¥)で継続する ことで、何行にもわたる長いマクロが定義できます。

ここでは、マクロの引数にダミー関数名を与えることで、

      MakeDummyFunction(win_bell)
と書くだけでダミー関数が1つ作れるようにします。マクロの置換は、単なる文字列の置換で、C 言語の文法に従って置換する訳ではありませんから、文字列中にも仮引数と一致する文字列がある と置換されます。これで、多数のダミー関数があっても、リスト6−2のようにすればあっという 間に作れるでしょう。

「せこい」と言われようが、こういう技巧も心得ていないと開発に余分な時間がかかります。

 
リスト6−2 マクロで大量生産したダミー関数

     1	#define MakeDummyFunction( type, fn )  \
     2	type    fn()    \
     3	{               \
     4	        fprintf( stderr, #fn "() is not supported\n" );   \
     5	}
     6	
     7	MakeDummyFunction( int,          win_bell )
     8	MakeDummyFunction( Notify_value, notify_next_event_func )
     9	MakeDummyFunction( int,          pw_batch )
    10	MakeDummyFunction( Event *,      canvas_window_event )

■デバッグライト■

デバッグ時に、関数の入口と出口、あるいは適当な個所に、その点の通過を示すメッセージを表 示させることはよくやるでしょう。デバッグ中はメッセージは必要ですが、最終的には表示されて は困るので、適当なマクロ名、たとえば DEBUGが#defineで定義されているときだけ表示されるよ うにします。すると、リスト6−3のようになりますが、#ifdefと#endifが目障りで、プログラム が読み難くなります。

 
リスト6−3 # 行のあるソース

     1	caddr_t menu_get( menu_obj, op )
     2	IN      myMenu_object   *menu_obj;
     3	IN      Menu_attribute  op;
     4	{
     5	        va_list         ap;
     6	        caddr_t         value = NULL;
     7	
     8	#ifdef DEBUG
     9	        printf( " >>> enter menu_get\n" );
    10	#endif
    11	
    12	        if( ! menu_obj ) {
    13	                printf( " menu_get() .. menu_object is NULL\n" );
    14	                goto ending;
    15	        }
    16	
    17	        switch( TYPE_OF_MENU_OBJECT( menu_obj ) ) {
    18	        case MENU_OBJECT_MENU:
    19	                value = smenu_get_menu( (myMenu*)menu_obj, op );
    20	                break;
    21	        case MENU_OBJECT_MENU_ITEM:
    22	                value = smenu_get_item( (myMenu_item*)menu_obj, op );
    23	                break;
    24	        }
    25	ending:
    26	
    27	#ifdef DEBUG
    28	        printf( " <<< exit menu_get\n" );
    29	#endif
    30	        return  value;
    31	}

また、#ifdefと#endifで囲むのが面倒だし、どうせすぐに消すからと、printfまたはfprintfな どで直接書いてしまうことも多いでしょう。

こういうとき、ヘッダーファイル中に、

      #ifdef DEBUG
      #define DebugPrint( message )   fprintf( stderr, message )
      #define DebugPrint2( f, a, b )  fprintf( stderr, f, a, b )
      #else
      #define DebugPrint( message )   
      #define DebugPrint2( f, a, b )
      #endif
と書いておけば、#ifdef,#endifがなくなり、はるかに読みやすくなります。単純なメッセージは DebugPrintを、データを伴うものはDebugPrint2を使ってみてください。

#ifdefと#endifがいっぱい出てくると、読みづらくてたまりません。これは、C言語特有の字下 げの効果を破滅させるからです。長いプログラム中に、#ifdefと#endifが頻繁に出てくるのは、た とえデバッグ中でもいやなものです。上の方法では、デバッグライトが字下げを妨害しません。こ の効果はリスト6−4に出ています。プログラムが長くなると、もっともっと差が歴然としますが、 それは想像してください。

 
リスト6−4 # 行を取り除いたソース

     1	caddr_t menu_get( menu_obj, op )
     2	IN      myMenu_object   *menu_obj;
     3	IN      Menu_attribute  op;
     4	{
     5	        va_list         ap;
     6	        caddr_t         value = NULL;
     7	
     8	        DebugPrintf( " >>> enter menu_get\n" );
     9	
    10	        if( ! menu_obj ) {
    11	                DebugPrint( " menu_get() .. menu_object is NULL\n" );
    12	                goto ending;
    13	        }
    14	
    15	        switch( TYPE_OF_MENU_OBJECT( menu_obj ) ) {
    16	        case MENU_OBJECT_MENU:
    17	                value = smenu_get_menu( (myMenu*)menu_obj, op );
    18	                break;
    19	        case MENU_OBJECT_MENU_ITEM:
    20	                value = smenu_get_item( (myMenu_item*)menu_obj, op );
    21	                break;
    22	        }
    23	
    24	    ending:
    25	        DebugPrint( " <<< exit menu_get\n" );
    26	        return  value;
    27	}

■無■

リスト6−3,6−4の2,3行の先頭に、INとありますが、これはこの関数への引数が入力で あることを示しているようです。当然、出力はOUT、入出力両方になる場合はINOUTでしょう。この ように書くと、わざわざコメントで入出力の区別を書かなくても良いので便利です。しかし、何の 細工もせずにこんなことをすると、もちろんエラーになります。これは、ヘッダーファイル中で、
        #define IN
        #define OUT
        #define INOUT
と定義しているため、プリプロセッサでマクロ展開されると無くなってしまい、何も書かなかった のと同じ、つまりコメントと同様になります。コメントは、/* と */ に囲まれているので、邪魔 くさい感じがします。C言語の予約語にみえるようにするためには、in, out, inout と小文字に することもできます。

何かに変換するだけがマクロの使い道ではありません。「無」への置換はいろいろ応用範囲が広 いので覚えておいてください。


Copyright1996 Hirofumi Fujiwara. No reproduction or republication without written permission
『Cプログラミング診断室』目次次(第6章 不慣れ ビットフィールド)