『Cプログラミング診断室』目次次(第8章 Pascalが好き 可変個数の引数)

第8章 Pascalが好き

Lisp?


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に文字を次々に追 加しています。おかげで、リストが黒々となっていて、とても重苦しい感じのするコーディングで す。

■下手な文字列処理■

リスト8−6の前半には、strlenとstrncatがいっぱいあります。上の悪例では文字数は意識す る必要があまりありませんが、こちらの方はstrlenがいっぱいあり、つねに追加する文字列長を調 べています。

前半のパターンは、

          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などの文字列処理関数を気楽に使っていますが、文字列の終了を示すヌル文字で文字が終 了していないと、簡単に暴走する関数なので、これらの関数は要注意なのです。

■sprintf■

配列に文字列を組み立てたいときは、かならず sprintfを使うようにしましょう。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	}


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