Pascalがあって、つぎがLispでは、「Cプログラミング診断室」ではないですね。でも、リスト 8−6の後半のstrcatの入れ子の連続(73〜82行)を見た時には、Lispか、それともSchemeかと思っ てしまいました。
この関数setparamの中に、極めて致命的なコーディングがあります。きっと、彼はこれで良いと 思っているでしょうが、とんでもない個所があります。興味のある方は、以下を読む前に自分で見 つけてください。
リスト8−6 Lisp みたいな C |
1 setparam( func ) /* parameter set function */ 2 char func ; /* function character */ 3 { 4 register int cnt, /* Parameter counter */ 5 ctmp ; /* temporary parameter counter */ 6 char *getid() ; /* Function status momory class definition */ 7 8 9 10 cnt = 0 ; /* Parameter counter initialize */ 11 12 if ( func == DTCT ) { 13 param[cnt++] = Job_ID ; 14 param[cnt++] = SPACE ; 15 ctmp = strlen( EOPT ) ; 16 strncat( param, EOPT, ctmp ) ; 17 cnt += ctmp ; 18 ctmp = strlen( getid() ) ; 19 strncat( param, getid(), ctmp ) ; 20 cnt += ctmp ; 21 param[cnt++] = SPACE ; 22 23 ctmp = strlen( F3OPT ) ; 24 strncat( param, F3OPT, ctmp ) ; 25 cnt += ctmp ; 26 ctmp = strlen( f3 ) ; 27 strncat( param, f3, ctmp ) ; 28 cnt += ctmp ; 29 param[cnt++] = SPACE ; 30 31 ctmp = strlen( F4OPT ) ; 32 strncat( param, F4OPT, ctmp ) ; 33 cnt += ctmp ; 34 ctmp = strlen( f4 ) ; 35 strncat( param, f4, ctmp ) ; 36 cnt += ctmp ; 37 param[cnt++] = SPACE ; 38 39 ctmp = strlen( F0OPT ) ; 40 strncat( param, F0OPT, ctmp ) ; 41 cnt += ctmp ; 42 ctmp = strlen( f0 ) ; 43 strncat( param, f0, ctmp ) ; 44 cnt += ctmp ; 45 param[cnt++] = SPACE ; 46 47 ctmp = strlen( F00OPT ) ; 48 strncat( param, F00OPT, ctmp ) ; 49 cnt += ctmp ; 50 ctmp = strlen( f00 ) ; 51 strncat( param, f00, ctmp ) ; 52 cnt += ctmp ; 53 param[cnt++] = SPACE ; 54 55 ctmp = strlen( F16OPT ) ; 56 strncat( param, F16OPT, ctmp ) ; 57 cnt += ctmp ; 58 ctmp = strlen( f16 ) ; 59 strncat( param, f16, ctmp ) ; 60 cnt += ctmp ; 61 param[cnt++] = SPACE ; 62 63 ctmp = strlen( F2OPT ) ; 64 strncat( param, F2OPT, ctmp ) ; 65 cnt += ctmp ; 66 ctmp = strlen( f2 ) ; 67 strncat( param, f2, ctmp ) ; 68 cnt += ctmp ; 69 } else if ( func == STDSIG ) { 70 param[cnt++] = Job_ID ; 71 param[cnt++] = SPACE ; 72 param[cnt++] = '\0'; 73 strcat(strcat(strcat(param,EOPT),getid())," "); 74 strcat(strcat(strcat(param,F00OPT),f00)," "); 75 strcat(strcat(strcat(param,F0OPT),f0)," "); 76 strcat(strcat(strcat(param,F1OPT),f1)," "); 77 strcat(strcat(strcat(param,F2OPT),f2)," "); 78 strcat(strcat(strcat(param,F3OPT),f3)," "); 79 strcat(strcat(strcat(param,F17OPT),f17)," "); 80 strcat(strcat(strcat(param,F18OPT),f18)," "); 81 strcat(strcat(strcat(param,"-I="),inp)," "); 82 strcat(strcat(strcat(param,"-C="),childname)," "); 83 cnt = strlen(param); 84 } 85 param[cnt++] = CR_CODE ; 86 param[cnt] = NULL ; 87 88 return( cnt ) ; 89 } |
関数strcatは、引数1の文字列に、引数2の文字列の複写を追加します。したがって、
strcat(strcat(strcat(str,"A"),"B"),"C");という関数のネストは、まず、一番内側のstrcat で、文字列strに"A"が追加され、strを返します。 したがって、2番目のstrcatにより、そのstrに "B"が追加されます。このstrcatもstrを返し、そ れが、一番外側のstrcatの引数1になります。だから、このstrcatは、すでに"A"と"B"が後ろに順 に追加されたstr(つまり、"AB")に、"C"を追加します。したがって、この3重のstrcatで、元の strの後ろに"ABC" がつながります。
リスト8−6の後半では、この3重のstrcatが10連になっていて、配列paramに文字を次々に追 加しています。おかげで、リストが黒々となっていて、とても重苦しい感じのするコーディングで す。
前半のパターンは、
ctmp = strlen( F3OPT ) ; strncat( param, F3OPT, ctmp ) ; cnt += ctmp ; ctmp = strlen( f3 ) ; strncat( param, f3, ctmp ) ; cnt += ctmp ; param[cnt++] = ' ' ;の形式の繰り返しです。F3OPTはマクロで、実際は文字列です。この処理は、
追加文字列の長さを求める paramに文字列を追加する 文字数を文字列長だけ増加する 追加文字列の長さを求める paramに文字列を追加する 文字数を文字列長だけ増加する 空白を1文字追加するの手順で行なっています。この1回分が、後半の1行のstrcatの3重ネストに対応しています。 このやり方も、ものすごいムダがいっぱいです。あまりに「馬鹿馬鹿」しいので、説明はやめま しょう。
7行でひとまとまりの処理をしているのですが、最後に空白を1文字追加した時点で、paramは どうなっているでしょうか。これは、最後に追加した空白までの文字列になっているでしょうか。
Cでは、文字列はヌル文字で終了することが決められています。最後に空白を入れ、文字数の cnt はインクリメントして正しい文字数になっています。しかし、空白の次がヌル文字である保証 は何もありません。この関数を呼び出す前に確実に文字配列param全体をヌルでクリアしていれば 動作しますが、万一忘れてしまうと、どうなるでしょう。
ゴミが残っていると、次のstrncatは、ゴミがヌルで終了するまでの長さの文字列と解釈し、そ のゴミの後ろに指定した文字列を追加します。したがって、配列全部がゴミだったりすると、確保 した配列よりさらに後ろに文字列を追加します。これでは、その配列範囲を越えてメモリ内容を変 更してしまいます。ふつう、配列の後ろにも何らかのデータが入っていますから、貴重なデータか 何かを壊し、暴走してしまいます。
strcatなどの文字列処理関数を気楽に使っていますが、文字列の終了を示すヌル文字で文字が終 了していないと、簡単に暴走する関数なので、これらの関数は要注意なのです。
printfは誰でも使っているでしょう。printfは編集結果が標準出力に出ます。fprintfは、指定 したストリームに出ます。それと同様に、編集結果を文字配列に書き込むのがsprintfです。
リスト8−7が、sprintfを使って書き換えたものです。あまりにも単純過ぎて、説明すること も不用でしょう。元とまったく同じ動作をするようにしているので、まだ汚いですが、プログラム 全体をきれいにすれば、もっと単純になり、この関数そのものが不用になってしまうような例でし た。
文字列の組み立てには、絶対にsprintfを使いましょう。これは常識です。
printfで出力書式を動的に複雑に変更する必要に迫られたりしたときは、printfの書式指定文字 列をsprintfで組み立てるなんて考えもあります。でも、ほとんど使うことはないでしょう。
リスト8−7 sprintfを使ってすっきりと |
1 /********************************************************************************/ 2 /* parameter set function */ 3 /********************************************************************************/ 4 setparam( func ) 5 char func; /* function character */ 6 { 7 char *getid(); /* Function status momory class definition */ 8 int cnt; 9 10 if ( func == DTCT ) { 11 sprintf( param, 12 " -E=%s -F3=%s -F4=%s -F0=%s -F00=%s -F16=%s -F2=%s\n", 13 getid(), f3, f4, f0, f00, f16, f2 ); 14 cnt = strlen(param); 15 param[0] = Job_ID; 16 } else if ( func == STDSIG ) { 17 sprintf( param, 18 " -E=%s -F00=%s -F0=%s -F1=%s -F2=%s -F3=%s -F17=%s -F18=%s -I=%s -C=%s\n", 19 getid(), f00, f0, f1, f2, f3, f17, f18, inp, childname ); 20 cnt = strlen(param); 21 param[0] = Job_ID; 22 23 } else { 24 sprintf( param, "\n" ); 25 cnt = strlen(param); 26 } 27 28 return cnt; 29 } |