フィンローダのあっぱれご意見番

第77回:最後の診断

初出: C MAGAZINE 1998年10月号
Updated: 1998-12-17

[一覧] [ホームページ]


 ブラックジャックという漫画がある。有名すぎるので説明もいらないだろう。その中に、コンピュータを治療する話がある。まるでSFのようだが、このコンピュータは人工知能によって自我が形成されていて、自分の執刀医にブラックジャックを指名するのだが、医者がコンピュータを直せるのだろうか。漫画の中の話だからといえばそれまでだが、ブラックジャックは部品を触診で発見し、交換、いや、手術だから移植だろうか、それをするのである。

 ブラックジャックの治療したのはハードウェアだ。残念ながら、手塚治虫氏はコンピュータのプロフェッショナルではなかった。だから、ソフトウェアの治療という発想はブラックジャックには出てこない。

 最近、医療という行為はソフトウェアの欠陥を除去する作業に似ていることに気付いた。残念ながら私には医学の知識は殆どない。ただ、ここに面白い本が一冊ある。アーサー・ヘイリーという人の小説で、「最後の診断」というタイトルで新潮文庫から出ている。老名医師が最新の技術を知らないばかりに誤診をして大変なことになるというストーリーなのだが、これはまるで最新の開発環境を知らずにバグを含んだプログラムを納品してしまったプログラマーを見るかのようだ。医学の世界もコンピュータの世界と同様に進歩の速度が激しいらしい。

 参考になるエピソードがたくさんあるのに驚いた。小説の中にはクロス・ファイルというものが出てくる。過去の症例のリストのことだ。患者が何の病気なのか、過去のファイルから類似のデータを検索し、それを参考に現状の判断を行うのだ。プログラミングでそのようなことは行っているか。プログラマーはバグを発見すると無造作にそれを修正するかもしれない。コメントすら残さないこともあるはずだ。しかし、バグを発見したらそれをリストアップすべきである。そのバグがなぜ発生したか。どのような現象によって発見されたか。どのように回避したか、それとも回避をあきらめたのか。

 ところが、おそらくクロスファイルに相当するバグリストを持っているプログラマーは少ないのではないかと思う。もちろん、プロジェクト単位で障害票を作成することはよくある、というか多分常にあるはずだ。しかし、それはあくまでプロジェクトの内部の話なのだ。プロジェクトを超越して、他のプロジェクトにも応用するためのバグリストを持っている人はどれだけいるだろうか。モーツァルトのように、それは俺の頭の中にあるんだ、という人がいるかもしれない。それも技術者である。身についたデバッグのノウハウが飯の種になるわけだが、そのノウハウをチームで共有したり、後の担当者に引き継ぐこともまた重要なのではないか。

 ミスをするのは避けられない。どうすればミスを減らせるか、ミスの影響を小さくできるか、発見率を高め、発見した時にどうやって対応すればよいか、そこまで考えておくのは当たり前である。新しいアプローチというのはいつも重要である。「今できているからこのままでいい」という自己満足は、ソフトウェアの世界でもやはり成り立たないはずだ。


 FPROGORGの「ROM墓場からの声」での話題。元はどこのネタなのか全然分からないのだが、意外と近い所だという噂もある。それはさておき、疑問は実に簡単。int main()とint main(void) は同じなのか、という問題である。

---- List 1 (mainの引数) ----
/* (1) 引数が空の場合 */
main()
{
    foo();
}

foo()
{
    main(0, 0);
}

/* (2) 引数が void の場合 */
int main(void)
{
    foo();
}

foo()
{
    main(0, 0);
}

 List 1に出てくる2つの例に対して、(1)はコンパイルしてもエラーにならないが(2)はエラーになるから、これは異なるのではないか、という主張である。エラーにならなくても、こんなの実行したら結果はどっちもどっちだというのはさておき、果たして本当にそうなのか、というのがどうも不安である。

 JIS X 3010における関数宣言子の定義は、「関数定義の一部の関数宣言で並びが空の場合、関数は仮引数を持たない。関数定義の一部でない関数宣言の並びが空の場合、仮引数の個数及び型の情報をもたない」(6.5.4.3) となっている。もっとも、main関数をわざわざ定義以外に宣言するというのは通常ありえないことだが。List 1の場合も、関数定義として同時に宣言されている例になる。従って、仮引数並びが空になっているということは「仮引数を持たない」ということであり、それは「並びの中の唯一の項目がvoidという特殊な場合、関数は仮引数をもたないことを指定する。」(同) と規約にある。ということは、voidと書いても何も書かなくても意味は同じということになりそうだ。

 すると奇妙なのは、コンパイルしたら片方だけエラーになるという事実だ。手元のgccで試したら、実際にそうなった。これはJIS X 3010では6.3.2.2において「実引数の個数が仮引数の個数と一致しない場合、その動作は未定義とする」となっているから、つまりいわゆる「鼻から悪魔が出ても構わない」という状況なのであり、たまたま片方で鼻から悪魔が出てきたのだ、という解釈はいかがだろうか。

 ちなみに、Windows 95上のgccを使ってオプションに -Wall -ansi を指定してコンパイルした場合、main()のように書いたソースの場合は警告が5個表示されるが、main(void)の場合はさらに「too many arguments to function `main'」というエラーメッセージが表示されることが分かる。


 ところで、C言語の入門書の誤りとして一般的によく話題になるのがmain関数の型である。標準のCでは、mainの関数はintであると決められている。つまり、Cではvoid main() というのは誤りである。ところが、Comp.lang.c の C FAQにも書かれているように、世の中にはなぜか異様にmainの型をvoidにしたがる人が多いようだ。しかもそういう本が結構ある。これに対してC FAQでは"C Programing for the Compleat Idiot"という本にはvoid main()と書かれているという質問に対して、"Perhaps its author counts himself among the target audience. "と皮肉っている。(http://www.eskimo.com/~scs/C-faq/q11.15.html)

 つまり、入門書をチェックする極めて手軽な方法で、かつ世界的にも支持されている方法があるわけだ。void main()と書いてないか? もし書いてあったら、その本は見捨てた方が無難だ。ANSIの規格を知っていたら、そんなことを書くわけがないからである。void main()という表現は、その人が標準Cの規格を知らないということを現している。つまり、その他にもたくさんある標準Cの規則を同様に知らない可能性を現しているのだ。

 ただし、これは標準Cの話だから、特定の処理系だけを対象にした入門書なら話は別である。void main()という書き方をした場合の結果は未定義と定められている。C言語の約束では、未定義というのは「どんな結果になってもいい」ということだから、その処理系では正しく動作することになっても構わないのである。それに、処理系によっては、void main()の動作保証どころか、intではなくvoidにしろという指定があるかもしれない。標準Cとしては正しくないが、特定の処理系という条件下では正しいわけだ。もちろん、そういうことを入門書に書くのなら最初から「特定処理系のC」というタイトルにしたうえで、標準Cではその書き方が正しくないことも併記して欲しいものだ。

 void main()派の反論を検討するというのも面白い。この議論は既にさんざん行われて枯れ尽くしているのだが、反論のパターンはそんなに多くないことに気付く。同じ主張が繰り返されているのである。例えば、一番よくあるものは、値を戻す必要がないからvoidでいいんじゃないか、という主張である。List 2のようなケースだ。exit(0);を呼び出しているから、処理系の後処理には0という値が使われるはず、というのである。

---- List 2 (値を戻さないmain関数) ----
void main()
{
   exit(0);
}

 これはもちろん規約違反だから正しくないのだが、気持ちは分かる。なぜこのプログラムが間違っているのかというと、いろいろ理由を付けることもできるが、結局、まさに規約に反するからとしか言いようが無いのである。言い換えれば、これでも問題ないという仕様を選択することもできたのである。とにかく、ANSIの規格を決める人達は、main関数はintであることを支持した。それが全てだ。

 もう一つ、よくある反論は、残念なことに「だってこの本に書いてあるじゃないか」というものである。あるいは大手メーカーのコンパイラのサンプルプログラムがそうなっているとか、つまり現実にそうなっているということと、それが見かけは一応何の問題もなく動いている、というセットで攻めてくるのだ。これは厳しい。


 「最後の診断」の中では、血液テストの悶着のシーンが出てくる。従来のテストだけ行っていれば、失敗することは滅多にない。滅多にないのだが、皆無ではないのだ。新しいテストを行えば、この「滅多にない」が「皆無」になる。それにも関わらず、今までうまくやってきたのだし、ベテランのスタッフは最新の方法を理解できない。そしてこのテストを拒否する。これはmainの型をvoidにしたがるという心理に実に似ている。間違いを指摘されても、「これで動いているからいいじゃないか」「今までもうまく行った」という考え方もある。

たかがvoidという所をintに書き換えるだけの作業でプログラムは正しいものとなる。必要なら最後にreturn 0;という文を突っ込めばよい。それができないというのは、他人から今までの自分のスタイルを否定されるというのは、ただごとではないという事実を示している。

 とはいえ、そういう人ははっきり言ってプログラマーに向いていないんじゃないかと思う。小説の中では失敗は人の命という結果に現れるが、プログラムのバグというのは、考えてみれば、それ以上のとんでもないことになる危険もあるわけだ。まあ、その時はその時、という程度でプログラムするのも気楽なのだが、そう考えることができる位なら、voidだろうがintだろうがさっさと正しい方に転んでおいた方が後は楽なんじゃないか。

(フィンローダ ニフティサーブ FPROG SYSOP)


(C) 1998 Phinloda, All rights reserved
無断でこのページへのリンクを貼ることを承諾します。問い合わせは不要です。
内容は予告なく変更することがあります。