従って、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か確認する作業が増えるだけだ。どうせ最初か
ら分かりにくいのなら、後で確認しやすいように書くのもアイデアである。