従って、List1のような記述はよくあるのだが…。
/* List1 */ #define MY_BUFSIZE 128 foo() { char buffer[MY_BUFSIZE]; gets(buffer); ... }
期待する回答は「128あれば十分だと思ったから」である。しかしそう回答する 人は少ない。おそらく128が十分である根拠がないことを、その人自身知っている からだと思う。
例えば数値をキーボードから入力したい場合を想像すれば、128桁の数値を入れ る人はまずいないのである。128桁というのはよほど冗長で、感覚的には無駄と言 ってもよい。16桁もあれば十分だ。なら16にしよう、というのも安易すぎるわけ で、「16なのはなぜですか?」と質問すればわかるように、やはり本質的な所は 何も変わっていないのである。
経験的には、128桁も用意して足りない場合というのは、うっかりキーボードの あるキーを押しっぱなしにするような事故の場合である。バッファをどんなに用 意ても、あふれてしまう危険は常に伴う。そこで、多少注意深い人であれば、gets は使わずにfgetsを使ってバッファのサイズを超えた入力を許さないようにプログ ラムする。この癖は初心者のうちに身に付けておいて損はないのだが、なぜか入 門書と称する本の著者の中にはgetsを好む方が多いような気がする。説明が簡単 になることは否定しないが…。
ところで、もう一つ本質的に疑問を感じるのは、次のような記述である。
char buffer[MY_BUFSIZE];この記述を見ただけで、bufferのサイズが何バイトかわかった人は超能力者で ある。そうでない人は、MY_BUFSIZEがどこかに定義されているのを捜して、その 値を見なければならない。例えば、20桁の入力の可能性のある処理なのに、何か の間違いでMY_BUFSIZEを16にしてしまった、という場合を考えてみよう。このバ グは、ソースプログラムでbufferを定義している所で気付くかどうか疑問が残る。
char buffer[16];こう書いてあれば、すぐ気付くかもしれない。しかし、このように書かない理 由は、それこそ入門書に書いてある。サイズを変更しようとした時に、片方を修 正し損ねる危険があるからだ。
fgets(buffer, 16, fp);このような記述がプログラムの離れた所にある時に、うっかり直し損ねるかも しれないのである。
*C言語で最もよく見かける定数で、かつ仕様としては明確な定義のないものは何 だろうか。当然意見の分かれる所である。EOFやNULLは、仕様上明確なので除外す る。私の一押しは"TRUE"と"FALSE"である。 この値は人によって定義が異なることがある。
いくら亜流があると言っても、C言語の性質によって暗黙の制限が発生する。C 言語の条件判断処理は、与えられた式の値が0か否かで判断する。従って、TRUEま たはFALSEのいずれかが、0であるべきだ。また、値として0と873を使う、という 人は変である。0でない方の値は、1か-1のいずれかだろう。ということは、考え られる選択は、List2の4種類の中の一つとなる。
/* List2 */ /* (1) */ #define TRUE 1 #define FALSE 0 /* (2) */ #define TRUE -1 #define FALSE 0 /* (3) */ #define TRUE 0 #define FALSE 1 /* (4) */ #define TRUE 0 #define FALSE -1
/* List3 */ /* a,b どちらと同じに見えるか? */ if (i) function(); /* a */ if (i == TRUE) function(); /* b */ if (i == FALSE) function();
if (hungry()) eat();この場合には、hungry() だと食事をする、といった解釈が自然である。逆のよ うな場合はあるだろうか? あるかもしれないが、とりあえずこの例に限って考 えれば、
if (hungry() == TRUE) eat();このように解釈すべきである。というのは、if文を見た時に、プログラマーは 「もし〜なら…を実行する」と読むのである。もともとifの条件判断においては、 このような暗黙の思い込みがある。
次のように書かれた場合を考えてみよう。
if (not_hungry()) eat();これを見て、瞬間的に「もし、not_hungryでなければeatする」と考えた人がい たら、上の仮定は間違っていたことになる。しかし、おそらく多くの人は、これ を見たその瞬間には、「もし、not_hungry なら、eat する」と解釈すると思う。 だが、意味上は、次のように解釈しないと直感的に不条理である。
if (not_hungry() == FALSE) eat();こうすれば、「もし、not_hungry ではない、というのであれば、eat する」と いうことになる。FALSEと等しいという状態を「〜が成立しない」という意味に解 釈するのである。しかし、これは不自然な解釈である。
ということは、if に続く式の値が真である場合には、TRUE を対応させた方が 混乱が避けられるのではないか、という結論にたどり着く。ということは、TRUE には 0 でない値を割り当てることになる。従って、FALSE が 0。これで残った選 択肢は2つになる。TRUE に -1 を割り当てるか 1 を割り当てるか。
C言語では、!0は1である。例えば、比較を行う二項演算子、例えば == による 演算の結果は 0 または 1 である、-1 という値は戻ってこない。従って、TRUE を 1 にすれば、(1) が最終選考に残る。経験的には、やはりこの組み合わせを選 ぶ人が多いようである。
*ところで、List4のようなプログラムを書いたことはないだろうか?
/* List4 */ if (strcmp(str, "hello")) world();
ここで、再び TRUEとFALSEの話に戻って、これらの定数が何なのかまだ知らな いという前提で、次の処理を考えて欲しい。
if (strcmp(str, "hello") == TRUE) world();「str と "hello" が一致した時には、world() を実行する」 という処理を実現 しようとして、このように書いた人は、strcmpの解釈に問題がある。
strcmpは、2つの文字列に「差があるかどうか」を調べ、その結果を戻すと考え ると分かりやすい。しかし、比較の結果「一致した」のが真で、「差があった」 のが偽であるという解釈も自然である。もしこれがstrdiffという名前であったら、 「与えた2つの文字列が異なるかどうかを調べる関数」であるという意味が強調 され、異なっていれば真、異なっていなければ偽を戻すのが自然になる。何のこ とはない、strcmpと全く同じ仕様で、言い方を変えればよいだけの話だ。
if (strdiff(str, "hello") == FALSE) world();こう書けば「strと"hello"とが異なっているかどうかを調べて、 異なっていない場合には、world()を呼び出す」と読むのは簡単である。
*昔ならやった記憶もあるが、今や私の場合はTRUEとかFALSEというマクロを定義 して定数を使うことは滅多にない。なぜなら、C言語の場合は、0が偽、1が真、と いう大前提で書いておけば、大抵間違いないからだ。そして、比較の時には0と比 較するのがコツである。strcmpの場合は?
if (strcmp(str, "hello") == 0) world();こう書く。これは一致したら実行する場合である。うっかりTRUEやFALSEと書い てしまうと、念のためTRUEが0か1か確認する作業が増えるだけだ。どうせ最初か ら分かりにくいのなら、後で確認しやすいように書くのもアイデアである。