『Cプログラミング診断室』目次次(第9章 珠玉の力作 力作)

第9章 珠玉の力作

文字列処理


もう文字列処理について何度取り上げたか分かりません。それにもかかわら ず、まだまだ新種が発見できる分野のようです。では、リスト9−5をご覧く ださい。

リスト9−5 オリジナルプログラム 文字列処理
     1  void SetUp_OutputFileName(poutputfile_path_name,
     2                               pjob_name, pfile_information)
     3  unsigned char           *poutputfile_path_name;
     4  unsigned char           *pjob_name;
     5  PFILE_INFORMATION       pfile_information;
     6  {
     7  /*1991.10.21  変更前開始*/
     8  /*
     9  if (pfile_information->type == FILE_TYPE_BLOCK_COPY)
    10      {
    11      strcpy((char *)poutputfile_path_name, PATH_OUTPUT_HOME);
    12      strcat((char *)poutputfile_path_name, "/");
    13      strcat((char *)poutputfile_path_name, (char *)pjob_name);
    14      strcat((char *)poutputfile_path_name, PATH_BLOCK_COPY_FILE);
    15      sprintf((char *)poutputfile_path_name + strlen(PATH_OUTPUT_HOME) + 1 + strlen((char *)pjob_name) + strlen(PATH_BLOCK_COPY_FILE) + 0,
    16              "%1X",
    17              pfile_information->information.block_copy.black_flag);
    18      sprintf((char *)poutputfile_path_name + strlen(PATH_OUTPUT_HOME) + 1 + strlen((char *)pjob_name) + strlen(PATH_BLOCK_COPY_FILE) + 1,
    19              "%1X",
    20              pfile_information->information.block_copy.plane_code);
    21      sprintf((char *)poutputfile_path_name + strlen(PATH_OUTPUT_HOME) + 1 + strlen((char *)pjob_name) + strlen(PATH_BLOCK_COPY_FILE) + 2,
    22              "%1X",
    23              pfile_information->information.block_copy.color_code);
    24      sprintf((char *)poutputfile_path_name + strlen(PATH_OUTPUT_HOME) + 1 + strlen((char *)pjob_name) + strlen(PATH_BLOCK_COPY_FILE) + 3,
    25              "%1X",
    26              pfile_information->information.block_copy.image_flag);
    27      sprintf((char *)poutputfile_path_name + strlen(PATH_OUTPUT_HOME) + 1 + strlen((char *)pjob_name) + strlen(PATH_BLOCK_COPY_FILE) + 4,
    28              "%1X",
    29              pfile_information->information.block_copy.border_flag);
    30      sprintf((char *)poutputfile_path_name + strlen(PATH_OUTPUT_HOME) + 1 + strlen((char *)pjob_name) + strlen(PATH_BLOCK_COPY_FILE) + 5,
    31              "%1X",
    32              pfile_information->information.block_copy.special_flag);
    33      strcat((char *)poutputfile_path_name, ".");
    34      strcat((char *)poutputfile_path_name, (char *)pfile_information->information.block_copy.ppage_no);
    35      }
    36  else if (pfile_information->type == FILE_TYPE_MASK)
    37      {
    38      strcpy((char *)poutputfile_path_name, PATH_OUTPUT_HOME);
    39      strcat((char *)poutputfile_path_name, "/");
    40      strcat((char *)poutputfile_path_name, (char *)pjob_name);
    41      strcat((char *)poutputfile_path_name, PATH_MASK_FILE);
    42      strcat((char *)poutputfile_path_name, ".");
    43      strcat((char *)poutputfile_path_name, (char *)pfile_information->information.mask.ppage_no);
    44      }*/
    45  /*1991.10.21  変更前終了*/
    46  /*1991.10.21  変更後開始*/
    47      if (pfile_information->type == FILE_TYPE_BLOCK_COPY) {
    48          strcpy((char *)poutputfile_path_name, PATH_OUTPUT_HOME);
    49          strcat((char *)poutputfile_path_name, "/");
    50          strcat((char *)poutputfile_path_name, (char *)pjob_name);
    51          strcat((char *)poutputfile_path_name, "/");
    52          strcat((char *)poutputfile_path_name, "R");
    53          strcat((char *)poutputfile_path_name, (char *)pfile_information->information.block_copy.ppage_no);
    54          strcat((char *)poutputfile_path_name, ".HAN");
    55      } else if (pfile_information->type == FILE_TYPE_MASK) {
    56          strcpy((char *)poutputfile_path_name, PATH_OUTPUT_HOME);
    57          strcat((char *)poutputfile_path_name, "/");
    58          strcat((char *)poutputfile_path_name, (char *)pjob_name);
    59          strcat((char *)poutputfile_path_name, "/");
    60          strcat((char *)poutputfile_path_name, "R");
    61          strcat((char *)poutputfile_path_name, (char *)pfile_information->information.mask.ppage_no);
    62          strcat((char *)poutputfile_path_name, ".MAS");
    63      }
    64  /*1991.10.21  変更後終了*/
    65  
    66  return;
    67  }

■コメント■

まず、9行目のif文はコメントの中です。7,45, 46,64行を見ると、1991.10.21 に変更したこと が分かりますね。7,45行には「変更前」とあり、変更前のソースが8〜44行にあることを示してい ます。

この関数の過半数がコメントですが、一目でコメントアウトされているのが分かりましたか。分 からないのが普通の人でしょう。コメント部分を取り除くと、リスト9−6になりました。かなり 「すっきり」したでしょう。

リスト9−6 修正版(その1)文字列処理
     1  void SetUp_OutputFileName(poutputfile_path_name,
     2                               pjob_name, pfile_information)
     3  unsigned char           *poutputfile_path_name;
     4  unsigned char           *pjob_name;
     5  PFILE_INFORMATION       pfile_information;
     6  {
     7      if (pfile_information->type == FILE_TYPE_BLOCK_COPY) {
     8          strcpy((char *)poutputfile_path_name, PATH_OUTPUT_HOME);
     9          strcat((char *)poutputfile_path_name, "/");
    10          strcat((char *)poutputfile_path_name, (char *)pjob_name);
    11          strcat((char *)poutputfile_path_name, "/");
    12          strcat((char *)poutputfile_path_name, "R");
    13          strcat((char *)poutputfile_path_name, (char *)pfile_information->information.block_copy.ppage_no);
    14          strcat((char *)poutputfile_path_name, ".HAN");
    15      } else if (pfile_information->type == FILE_TYPE_MASK) {
    16          strcpy((char *)poutputfile_path_name, PATH_OUTPUT_HOME);
    17          strcat((char *)poutputfile_path_name, "/");
    18          strcat((char *)poutputfile_path_name, (char *)pjob_name);
    19          strcat((char *)poutputfile_path_name, "/");
    20          strcat((char *)poutputfile_path_name, "R");
    21          strcat((char *)poutputfile_path_name, (char *)pfile_information->information.mask.ppage_no);
    22          strcat((char *)poutputfile_path_name, ".MAS");
    23      }
    24  }

変更前のものを、いつまでもコメントとして残している人がよくいます。それが何度も繰り返さ れてコメントだらけになり、どこが「生きた部分」か判然としないプログラムがあります。

SCCSとかRCSのようなソースプログラム管理ツールを活用しましょう。このようなツールを使う と、誰が、いつ、どこを、どのように変更したかを全部管理してくれます。

「何年何月何日のバックアップ」という名のテープやフロッピーがゴロゴロしていたり、ディレ クトリ名が日付になっていて、ハードディスクの中をそういうもので一杯にするのは止めましょう。 こんなことをしたら、管理するだけで疲れてしまいます。

最新のソースには、できるだけ過去の遺物(あるいは汚物、産業廃棄物かな?)を残さないよう にしましょう。UNIXを使いながら、このようなゴミの残し方をしているのでは、UNIXで開発してい る意味がありません。

■超非効率■

リスト9−5の11行目から見ていきましょう。コメント中ですが、すばらし いアイデアがあります。解説を読む前に、ご自分で、コメント中は何をやろう としたのか考えてください。理解できたときに、椅子から転げ落ちてケガしな いように注意してください。

分かりましたか。分かった人は、笑うなり、感心するなりしていてください。 では解説を始めます。

引数poutputfile_path_nameで指示された文字列バッファに、出力ファイル のパス名を編集して入れているようです。strcpyとstrcatを使って文字列を次々 に繋いでいくのはよく見かける下手な例です。11〜14,33,34行がそうです。 strcpyとstrcatの連続は、1つのsprintfに直せることは今までに何度も説明し ました。

ここで読みづらいのは,15〜32行の6連続sprintfの強力さです。特に、編集 後の文字列を入れるアドレスの第1引数がメチャメチャ長いのです。バッファ の先頭を示すpoutputfile_path_nameに5回加算しています。その5つの加算 の意味は何でしょう。

a) + strlen(PATH_OUTPUT_HOME)
11行でPATH_OUTPUT_HOME(/で始まる、あるディレ クトリ文字列にマクロ定義されている)をstrcpyして いるのに対応。

b) + 1
12行で、"/"をstrcatしているのに対応。

c) + strlen((char *)pjob_name)
13行で、pjob_name(たぶん「ジョブ名」)をstrcat しているのに対応。

d) + strlen(PATH_BLOCK_COPY_FILE)
14行で、PATH_BLOCK_COPYFILE(これも、ある文字 列にマクロ定義されている)をstrcatしているのに対応。

e) + 0
a)〜d)の加算で11〜14行の文字列処理でできあがっ た文字列の終わりのヌル文字のアドレスを示していま す。それに+0しているので、終わりのヌル文字の位置 から、書式に従った編集結果の文字列を書き込むので、 結局この長い加算は、それまでに作り上げた文字列の 直後に編集した文字列を繋いで書き込むことを意味し ます。

さて、最初のsprintfは、どのような文字列を作り上げるのでしょうか。書式が"%1X"ですから、 int型の値を、長さ1の文字列に変換します。ということは、15行のsprintfで文字列が1文字分長 くなりました。

18行のsprintfの第1引数は、15行のsprintfとほとんど同じです。最後が +1 になっている個所 だけが違います。これは15行のsprintfで、文字列が1文字長くなったので、その分を足していま す。このsprintfの書式も同じなので、sprintfの結果、文字列はさらに1文字長くなります。した がって、その次のsprintfでは、第1引数の最後が +2 になっています。

以下同様にして、6連続sprintfで6つのデータを各1文字で示し、最後に追加しています。どの sprintfでも、第1引数は編集バッファの文字列の終りのヌル文字を指しています。これは文字列 バッファの先頭アドレスに、その文字列の長さを加えれば求まりますから、どの第1引数も、

    poutputfile_path_name + strlen(poutputfile_path_name)
  
に変更できます。もちろん、文字列を与えると、その文字列の終りを示すヌル文字は、文字列関数 ライブラリのstrchrまたはindexを使って、
    strchr( poutputfile_path_name, '\0' )
  
とすることもできます。

ところで、sprintfでは、普通は複数のデータを一気に文字列にしてしまうのですが、このプロ グラマは1つのsprintfで1つのデータしか処理していません。この6連続sprintfは、書式を

	"%1X%1X%1X%1X%1X%1X"
  
とし、6つのデータを引数でまとめて指定すれば、1つのsprintfで済みます。

sprintfを最も非効率的に使った例でした。プログラムは長くなるし、読みにくいですね。 strlenを何度も使っているので、実行速度もぐーんと落ちます。

sprintfが6つ整然と並んでいるので、このプログラマには「美的」に見えるらしいです。でも、 世のまともなプログラマの100%が逃げ出すような美的センスと言えるでしょう。

■普通のsprintf

コメントを削ったリスト9−6は、strcpyとstrcatの連続になっています。こういうのを一発で 片づけてしまうときにsprintfを使いましょう。普通の書き方に治すと、リスト9−7になります。

リスト9−7 修正版(その2)文字列処理
     1  void SetUp_OutputFileName( poutputfile_path_name, pjob_name, pfile_information )
     2    char                  *poutputfile_path_name;
     3    char                  *pjob_name;
     4    FILE_INFORMATION      *pfile_information;
     5  {
     6          switch( pfile_information->type ) {
     7          case FILE_TYPE_BLOCK_COPY:
     8                  sprintf( poutputfile_path_name, "%s/%sR%s.HAN",
     9                           PATH_OUTPUT_HOME, pjob_name, 
    10                           pfile_information->information.block_copy.ppage_no );
    11                  break;
    12          case FILE_TYPE_MASK:
    13                  sprintf( poutputfile_path_name, "%s/%sR%s.MAS",
    14                           PATH_OUTPUT_HOME, pjob_name,
    15                           pfile_information->information.mask.ppage_no );
    16                  break;
    17          }
    18  }

オリジナルでは、どういう意図かは知りませんが、文字列を char *ではな く、unsigned char *にしています。そして、strcpyやstrcatで引数の型が一 致しないので、(char*)でキャストし、型合わせしています。でも、こんなこ とをするくらいなら、全部 char * にした方が簡潔になります。

多数の関数呼出しとゴミが詰まった関数が、たった2つのsprintfだけになっ てしまいました。相当分かりやすくなったでしょう。


Copyright1996 Hirofumi Fujiwara. No reproduction or republication without written permission
『Cプログラミング診断室』目次次(第9章 珠玉の力作 力作)