-148-
昨年 11 月号に、「tail を作れ」という問題が出題された。遊びのつもりで作 ってみたら、結構複雑になってしまう。こんな問題を初心者向け講座で出してよ いのだろうか、と疑問を感じたのだ。ところが、91-2 月号に掲載された解答例は、 しばし呆然自失せざるを得ないものだった。
解答例として 2 名の作品が掲載されていた。いずれも、初心者とは思えない出 来ばえで、多分、この講座をうなりながら読んでいる人たちとは、一線を越えた レベルには違いない。
このうち、藤澤氏の作品は、メモリをどんどん獲得してゆき、なくなったらお しまい、というあたり、修行中という印象を感じさせる。argc を使っていないの で、コンパイル時に警告が出るのだそうだが、そういう時は、main の最初の方で、
argc = argc;とでも書いておく手がある。:-)
メモリがなくなったら処理が中断する点については、明らかに改良の余地があ るが、まだ、仕様だと言われればそうかな、といったレベルの問題である。ただ、 このプログラムには、もう一つ気になる所があって、メモリが不足した時には、 exit(0) を実行しているにもかかわらず、正常に実行した場合に void main() の 最後から何も戻さずに終わってしまう(*1)。これはバグである。もちろん、異常時に は exit(1);、正常時には exit(0); とするのがよい。
さて、ここまでは前座で、呆然としたというのは、もう一方の作品である。
匿名希望の God Hand 氏の作品である。コメントを入れて 53 行の短いプログ ラムである。出題者の評は、「簡潔にプログラムを書いている」となっている。 これだけなら、別に何も感じなかっただろう。しかし、その前に書いてあること が猛烈に気になった。God Hand 氏は、プロのプログラマーであると言うのだ。
すわ同業者である。確かに、このプログラムは短くまとまっているし、動作も 問題の条件には一応合致している。出題者も指摘している通り、malloc でメモリ を獲得できなかった時の処理がないというのは気になるが、同業者のよしみで、 ついうっかりしたということにして妥協しよう。:-) では何も問題ないではない か、ということになるが、…
私の評価では、このプログラムは、アマチュアの作品ならよくできていると思 う。初心者とすればなおさら、中級者でも、まあまあではないか。しかし、プロ の作品としては全く落第だ。
プロなのに、こんなプログラムを書いたのか、ということに対してまず呆然と したのである。なぜなら、このプログラムは、最後の数行を表示するために、フ ァイルの最初から全ての行を読み込んでいるのだ。少し考えてみても、これ以上 能率の悪い処理を作れと言われてもちょっと難しい。つまり、安易な処理とコン パクトなコードを実現する代償に、根本的なアルゴリズムの選択に失敗している のだ。これはクイズの回答だからと言って済まされない問題ではないだろうか。 もちろん、アマチュアなら別であるが。
しかも、問題が掲載された号、Cマガジンの 11 月号の入門記事は、fseek() の解説から始まっているのである。fseek() とは、ファイルの任意の位置にファ イルポインタを移動する処理である。この関数を使えばファイルの最後からデー タを読み込んで処理することが可能である。
行ごとのポインタの処理の方法も煮詰まっていない。ポインタのテーブルが一 杯になると、配列の要素を順にコピーしてずらしている。最後に更新したインデ ックスを覚えておき、キューのように処理すれば、毎回のコピーは不要になる。
結局、この解答例は、長いテキストになればなるほど、リニアに処理時間が増 えるという性質を持っている。数十行程度なら、どうでもいい差かもしれないが、 ちなみに、UNIX の処理系を使って手元にあった約1万行のテキストを処理して time にかけ、パフォーマンスを調べてみた。ユーザープロセスに使われた CPU time が 3.4 秒、カーネルの CPU time は 0.5 秒かかっている。UNIX には標準 で tail が最初から用意されているので、これを使ってみると、ユーザの CPU time は 0 秒、カーネルの CPU time は 0.1 秒である。全く勝負にもならない程の性 能差である。
1万行のテキストなんて、滅多にあるものではないと思うかもしれないが、電 子会議のログを考えなしに作っていると簡単にできてしまうものだ。
実際に試してみても、標準の tail だと一瞬で表示が出てくるのだが、Cマガ ジン版の tail だと、結構待たされるのが感じられる。これは、内部処理が、明 らかに異なっていることを意味するのではないか。
このような背景や結果があるにもかかわらず、先頭から 1 行ずつ処理してしま った最大の理由は何か。問題文に、「ファイル名を指定しないときは、標準入力 から入力できること」という条件があったからかもしれない(標準入力は fseek できない)。しかし、それは標準入力の時だけ特別扱いすれば済むことである。
*さて、このプログラムが簡潔である、と評者は述べているが、まだまだ甘い。 アマチュアのレベルでは十分簡潔な方かもしれないが、プロでもこれで十分だと 言えるかどうかは議論の余地がある。例えば、
< fp=( fp==(FILE *)0 )?stdin:fp;は(*2)、私なら、
if (fp == NULL) fp = stdin;と書く。この方が、たとえ行数が多いとしても、簡潔だと思う。
邪悪なCプログラムコンテストの1行作品を見れば一目瞭然なように、簡潔と は、少ない文字数で書くことではないし、少ない行数で書くことでもないのだ。 この点はCマガジンの採点者が誤解しているかもしれない。他にも簡潔とは思わ れない記述がみられるからだ。
少しCに慣れた中級者の中に、使わなくてもいいのに3項演算子を使う人が多 いようである。実は自分自身そうだったかもしれない。上の場合は、fp が NULL でない時に、fp に fp を代入することになるが、これは無意味である。実際の処 理では、最適化されて代入が省略されるかもしれないが、それはプログラマが仕 事したのではなく、オプティマイザがえらかったといいたい。最適化されなくて も、プログラマーがこのようなコードをわざわざ書いたのだから、文句を言うわ けにはいかないのだ。i = i; という処理をプログラムの途中で書いて、最適化さ れないと文句を言うわけにはいかない。こんな自明な無駄は、最初から書かなけ ればいいのである。
しかし、これらのモヤモヤした感触は、多分 God hand 氏も感じていたのだろ う。それでなお、わざとこのレベルのものを提出したのかもしれない。プロなら それ位の joke はやるのだ。でも、もし私がこのプログラムを提出するのなら、 プロであることはひたすら隠したくなるだろう。
偉そうな事を書いているが、ではおまえはどうだと言われると、先日も、BPL のソースでオプションの処理をする際に、
my_param.flags |= FLAGS_BINARY;とすべき所を、
my_param.flags &= FLAGS_BINARY;と書いただけでなく、あまつさえそのままデータライブラリに登録してしまっ たというのだから救いようがない、まだ初心者といった所か。
(つづく)
(*2) fp == (FILE *) 0、という所のキャストは不要なはずである。もっとも、あっても悪くはない。
COMPUTING AT CHAOS RUINS -148- 1991-04-17, NIFTY-Serve FPROG mes(3)-075 FPROG SYSOP / SDI00344 フィンローダ (C) Phinloda 1991, 1996