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

第9回:直接数値を書いても悪くはない場合

初出: C MAGAZINE 1993年1月号
Updated: 1996-02-24

[1つ前] [1つ後] [一覧] [ホームページ]


定数というのは、その値が変わらないから定数という。というのは当り前のよ うだが、ではconstと指定した変数の存在意義は何だ、というと話がややこしくな る。また、ある入門書を読めば、「定数の値は後で変更したいことがあるので、 マクロで定義して使うとよい」と書いてあったりする。

従って、List1のような記述はよくあるのだが…。


/* List1 */

#define MY_BUFSIZE 128

foo()
{
    char buffer[MY_BUFSIZE];

    gets(buffer);
        ...
}

この種のプログラムを見掛け た時に、私はよく次のことを質問する。「bufferのサイズが128なのはなぜですか?」

期待する回答は「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の最初に書いたif 文は、a、bのどちらと 同じであると解釈するのが自然だろうか。
/* 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();

strcmpは、比較の結果一致すれば0を戻し、不一致なら0以外の値を戻す。List4 は、一見「str が "hello" と一致すれば、world() を実行する」 と読んでしまいそうだが、真実はそうではなく、「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か確認する作業が増えるだけだ。どうせ最初か ら分かりにくいのなら、後で確認しやすいように書くのもアイデアである。
(C) 1993, 1996 Phinloda, All rights reserved
無断でこのページへのリンクを貼ることを承諾します。問い合わせは不要です。
内容は予告なく変更することがあります。