もう文字列処理について何度取り上げたか分かりません。それにもかかわら ず、まだまだ新種が発見できる分野のようです。では、リスト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%が逃げ出すような美的センスと言えるでしょう。
コメントを削ったリスト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だけになっ てしまいました。相当分かりやすくなったでしょう。