オプション 処理 -F0=str f0pathlist=str -F00=str f00pathlist=str -F16=str f16pathlist=str -F2=str f2pathlist=str -F3=str f3pathlist=str -F4=str f4pathlist=strFに続く数字の判定を、まず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; |
-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を見てから、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のソースプログラムを参照してください。