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 }
|