『Cプログラミング診断室』目次次(第8章 Pascalが好き Lisp?)

第8章 Pascalが好き

オプション文字列処理


■文字比較■

関数chkarg(リスト8−1)は、パラメータを調べ、適当な処理をします。とくに、-で始まるオ プションの処理をしています。case 'E', case 'W', case 'F' などがそれぞれ、-E, -W, -F に対 応しています。そのなかで、とくに -F に対する処理が35〜85行にわたり長々と書かれています。 -Fに続いて数字があり、=があり、その後ろに続く文字列へのポインタを数字に対応する変数に代入 しています。つまり、以下に示すように、-Fで始まるオプションは7種類あり、=の直後の文字列 を対応する変数が指すようにします。
    オプション        処理
    -F0=str         f0pathlist=str
    -F00=str        f00pathlist=str
    -F16=str        f16pathlist=str
    -F2=str         f2pathlist=str
    -F3=str         f3pathlist=str
    -F4=str         f4pathlist=str
Fに続く数字の判定を、まず1文字目をswitch文で分け、さらに1文字目が0と1のとき、2文字 目をif文で調べています。そして、それぞれの場合に、その次に、ちゃんと=が続いているか調べ ています。

このコーディングをどう感じますか。普通と感じますか。ここでの処理は、非常に規則的なこと でしょう。

たぶん、1つのパターンを作ってから、次々とコピーしては変更することで増やしていったので しょう。作業そのものは単純だし、誰にも考えつくことで、疑問を持たない人も多いようです。で も、これでは、何のために、サブルーチンとか関数という概念が作られたのか全然分かってないこ とになります。同じようなことを、どんどん書くくらいなら、関数にまとめるべきなのです。

この「まとめる」という意味には、単にまとめるとプログラムが小さくなるだけではありません。 以前にも説明しましたが、同じことや、同じようなことを何度も書いている場合、その部分に修正 が必要になってきたら、全部の個所を修正しまくらなければなりません。関数とか、マクロとかで リスト上で1個所にまとめると、そこだけの修正で済みます。まとめてないと、必ずどこか修正を 忘れ、バグの原因になります。

もっとひどいときには、本来同様のものであったのが、別々の方法で修正され、完全に別物に化 けてしまうことです。こうなってしまうと、後でリストだけ見た人は、それらをまとめることはで きなくなり、ブクブクとプログラムが腫れてきます。

リスト8−1に戻りましょう。ifやswitchがいっぱい出てきて、ずいぶん長ったらしくなってい ます。各caseの中はほとんど同じなので、何か工夫をすれば単純になりそうです。

リスト8−1とリスト8−2だけでは、まだ不明なマクロや変数など残ります。それらはリスト 8−3にありますので、これから動作を調べてください。

 
リスト8−3 残りのマクロ定義と変数宣言

     1	#define  OPTION         '-'
     2	#define  ASCII          0x7f
     3	#define  NO_ERROR       0
     4	#define  E_IFORKP       0xe6
     5	
     6	#define  PARAM_MAX     1
     7	#define  PARAM_MIN     1
     8	#define  JOB_ID        0
     9	char     *param[PARAM_MAX];
    10	int      paramnum;
    11	int      dspsttid;
    12	char     *dirname;
    13	char     *f00pathlist   = NULL;
    14	char     *f0pathlist    = NULL;
    15	char     *f2pathlist    = NULL;
    16	char     *f3pathlist    = NULL;
    17	char     *f4pathlist    = NULL;
    18	char     *f16pathlist   = NULL;
    19	int      jobid;

■文字列比較

1文字づつ比較していたから良くなかったとしたら、文字列として比較してはどうでしょう。 -Fのオプションの形式は、
        -F0=文字列
で、ポインタが-Fの次を指しているとしたら、ポインタ以降に文字列 "0=" が続いていることをチェッ クすればいいのです。そして、そのときに= 以降の文字列を示すポインタを入れる変数のアドレス &f0pathlist と、"0="とが対応していればよいのです。これはもう構造体しかありませんね。

0 以外も続きます。その他のものも全部入れた構造体のデータを示します。

    typedef struct {
            char    *nn;
            char    **path;
    } fnn_type;
    static  fnn_type  fnn_data[] = {
        { "0=",  &f0pathlist  },
        { "00=", &f00pathlist },
        { "1=",  &f1pathlist  },
        { "16=", &f16pathlist },
        { "2=",  &f2pathlist  },
        { "3=",  &f3pathlist  },
        { "4=",  &f4pathlist  },
    };
構造体の要素数は7ですが、これはリスト8−4の先頭のマクロNumberの引数に配列名 (fnn_data)を入れると自然に求まりますね。これは、第1回目で説明しています。

 
リスト8−4 修正 その1(リスト8−1がオリジナル)

     1	#define  Number(ary)  (sizeof(ary)/sizeof(ary[0]))
     2	
     3	/********************************************************************************/
     4	/*      Parameters check                                                        */
     5	/********************************************************************************/
     6	chkarg( argc , argv )
     7	  int   argc;
     8	  char  *argv[];
     9	{
    10	        typedef struct {
    11	                char    *nn;
    12	                char    **path;
    13	        } fnn_type;
    14	        static fnn_type fnn_data[] = {
    15	                { "0=",  &f0pathlist  },
    16	                { "00=", &f00pathlist },
    17	                { "16=", &f16pathlist },
    18	                { "2=",  &f2pathlist  },
    19	                { "3=",  &f3pathlist  },
    20	                { "4=",  &f4pathlist  },
    21	        };
    22	        char    *ptr;
    23	        int     len;
    24	        int     i;
    25	
    26	        for( paramnum=0 ; ptr = *++argv, --argc>0 ; ) {
    27	                if ( *ptr == '-' ) {                    /* Option? */
    28	                        switch( toupper( *++ptr ) ) {
    29	                        case 'E':
    30	                                if(*++ptr != '=')
    31	                                        xexit (E_IFORKP);
    32	                                dspsttid  = atoi(++ptr);
    33	                                break;
    34	                        case 'W':
    35	                                if (*++ptr != '=')
    36	                                        xexit (E_IFORKP);
    37	                                dirname = ++ptr;
    38	                                break;
    39	                        case 'F':
    40	                                for( ++ptr, i=0 ; i<Number(fnn_data) ; ++i ) {
    41	                                        len = strlen( fnn_data[i].nn );
    42	                                        if( strncmp( ptr, fnn_data[i].nn, len ) == 0 ) {
    43	                                                *fnn_data[i].path = ptr + len;
    44	                                                break;
    45	                                        }
    46	                                }
    47	                                if( i == Number(fnn_data) )
    48	                                        xexit (E_IFORKP);
    49	                                break;
    50	                        case '?':
    51	                                syntax();
    52	                                exit( NO_ERROR );
    53	                        default:
    54	                                syntax();
    55	                                exit( E_IFORKP );
    56	                        }
    57	                } else {
    58	                        paramnum++;
    59	                        jobid = atoi(ptr);              /* Set Job-ID (task-ID) */
    60	                }
    61	        }
    62	
    63	        if( paramnum != 1 ) {
    64	              syntax();
    65	              exit( NO_ERROR );
    66	        }
    67	}

最後のデータ

        { "4=",  &f4pathlist  },
には、最後に コンマ(,)がついています。エラーになるような気がするでしょうが、エラーにはな りません。というより、最後にコンマをつける例は、カーニハンとリッチーの「プログラミング言 語C」にも出てきます。

最後の行のデータにもコンマをつけることで、全てのデータ行が、最後のコンマも含めて同じ形 になっているでしょう。このおかげで、最後の行を

        /* { "4=",  &f4pathlist  }, */
とコメントアウトしても、この行を気持ち良く消去しても、コンパイルエラーにはなりません。こ のために、最後にもコンマをつけているのです。

C言語というのは、コンマ1つまで注意深く検討されてできています。修正に、コンマ1つ余計 にエディタで修正する手間を省いてくれています。というより、最後にコンマをつけても良くして おくと、修正時の無駄なコンパイルエラーが減少するためでしょう。心憎いまでの配慮ですね。

比較そのものは、strncmpを使えば良いだけです。そのときの長さは、与えたデータから求めて います。比較はforループで、ループ回数は、配列のデータ数で決まります。最後まで回ってしまっ たら、見つからなかったのでエラーです。 このプログラムでは、引数リストに、- がつかないジョブ番号が1つ必要で、その前後どちらに でもオプションをつけることができます。

■汎用化■

-Fのオプションだけを、データの用意だけで処理できるようにしましたが、その他のオプション の場合にも拡張できないでしょうか。

引数文字列のポインタがFを見てから、switchで分かれていますが、このswitchをなくしてしま い、全体をfor文だけにすれば可能になります。ただし、オプションデータが文字列だけではなく、 数値もあります。そのために、データの構造体に、データの型を示すメンバーを追加します。

ヘルプメッセージを出すための、"-?"オプションも、いっしょに扱えるようにしてしまいましょ う。そのために、型の中に、整数、文字列、ヘルプの3種を入れてしまいましょう。データ型の識 別は、実際はint型のデータ(フラグ)で行なわせます。その型を数字の 1, 2, 3 で指定するので は分かり難いので、列挙型(enum)を使ってみましょう。変更したプログラムが、リスト8−5です。 オプション文字は小文字だけになっています。

オプションの最初の'-'も、識別オプション文字列に含めると、もっと簡潔になります。それは、 読者への練習問題としておきます。

 
リスト8−5 修正 その2(リスト8−2がオリジナル)

     1	#define  Number(ary)  (sizeof(ary)/sizeof(ary[0]))
     2	
     3	/********************************************************************************/
     4	/*      Parameters check                                                        */
     5	/********************************************************************************/
     6	chkarg( argc , argv )
     7	  int   argc;
     8	  char  *argv[];
     9	{
    10	        enum { String=1, Integer, Help };
    11	        typedef struct {
    12	                char    *opt;
    13	                int     type;
    14	                char    **save;
    15	        } opt_type;
    16	        static opt_type opt_data[] = {
    17	                { "e=",   Integer, &(char*)dspsttid    },
    18	                { "w=",   String,  &dirname     },
    19	                { "f0=",  String,  &f0pathlist  },
    20	                { "f00=", String,  &f00pathlist },
    21	                { "f16=", String,  &f16pathlist },
    22	                { "f2=",  String,  &f2pathlist  },
    23	                { "f3=",  String,  &f3pathlist  },
    24	                { "f4=",  String,  &f4pathlist  },
    25	                { "?",    Help,    NULL         },
    26	        };
    27	        char    *ptr;
    28	        int     len;
    29	        int     i;
    30	
    31	        for( paramnum=0 ; ptr = *++argv, --argc>0 ; ) {
    32	                if ( *ptr == '-' ) {                    /* Option? */
    33	                        for( ++ptr, i=0 ; i<Number(opt_data) ; ++i ) {
    34	                                len = strlen( opt_data[i].opt );
    35	                                if( strncmp( ptr, opt_data[i].opt, len ) == 0 ) {
    36	                                        switch( opt_data[i].type ) {
    37	                                        case String:
    38	                                                *opt_data[i].save = ptr+len;
    39	                                                break;
    40	                                        case Integer:
    41	                                                *(int*)(opt_data[i].save) = atoi(ptr+len);
    42	                                                break;
    43	                                        case Help:
    44	                                                syntax();
    45	                                                exit( NO_ERROR );
    46	                                        }
    47	                                        break;
    48	                                }
    49	                        }
    50	                        if( i == Number(opt_data) ) {
    51	                                syntax();
    52	                                xexit(E_IFORKP);
    53	                        }
    54	                } else {
    55	                        paramnum++;
    56	                        jobid = atoi(ptr);              /* Set Job-ID (task-ID) */
    57	                }
    58	        }
    59	
    60	        if( paramnum != 1 ) {
    61	              syntax();
    62	              exit( NO_ERROR );
    63	        }
    64	}

さて、このくらい変更すると、最初から比べるとはるかに便利でしょう。でも、まだまだ便利に できます。リストは示しませんが、改良方針を述べておきます。ちょっと内容的に高度になってし まいますが、ごめんなさい。まあ、初級者はどんなことをやるかくらいに読んでください。

まず、オプションのデータを、関数の内部に抱えこむのではなく、オプションを引数で渡して処 理してくれると便利ですね。このとき、元のargc, argvから、オプションの部分だけを抽出し、残 り物だけをargcとargvに残してくれると、オプション抽出関数として本物になります。

そして、もっと、もっと機能強化されているのが、Xウィンドのツールキットの初期化関数の XtInitializeでしょう。mainのargc,argvを与えると、以上の抽出をしてくれます。これ以上のこ とは、Xウィンドのマニュアルを見てください。この関数では、オプションの値は、Xのリソース になります。詳しくは参考文献[8-1][8-2]とXのソースプログラムを参照してください。


Copyright1996 Hirofumi Fujiwara. No reproduction or republication without written permission
『Cプログラミング診断室』目次次(第8章 Pascalが好き Lisp?)