Cygwinでデバッグ

数学の表記との違い

等号と代入、そして lvalue

数学で考えると、次の2つの等式は同じ意味です。

(1) a = 2
(2) 2 = a

さて、C言語のプログラムで考えると、少し事情が変わります。もしこれらが、if や while の条件式の中に現れて、左右の項を比較しているのだとすればどうでしょう。C言語での比較は == と2文字で表記することに注意します。

(1') if (a == 2) { ... }
(2') if (2 == a) { ... }

このように書けば、(1')(2') とも同じ意味になります。

しかし、もし代入を意味していれば、状況が異なります。代入の向きは右から左と決まっているので、以下の (1' ') は a に 2 を代入します。ところが (2' ') は文法エラーになります。2 に a の値を代入することはできないからです。

(1'') a = 2;  // OK
(2'') 2 = a;  // NG

これは、代入される(できる)ものは特別なものである、ということでもあります。= の左側の式は、英語では "lvalue" という、普通の辞書には載っていない単語で表されます。left value の省略されたものですので、日本語では「左辺値」との訳語があてられます。

左辺値は変数であればよいのかというと、そうでもありません。変数単独であればよいのですが、変数を用いた計算式には代入ができません。

a   = 2; // OK
a+1 = 3; // NG

同じように左辺値として x は大丈夫でも、x*x はエラーになります。もし x*x をある値にしたいのであれば、その逆関数である平方根を用いて x に代入するように式変形します。

(a) x*x = 4;       // NG
(b) x =  sqrt(4);  // OK
(c) x = -sqrt(4);  // OK(?)

(b)(c) のように、逆関数が1通りでない場合にどうすればよいのかは、プログラマが判断せねばなりません。

不等号

数学では「0≦x<10」のような表記をしますが、C言語の条件文としては、この表記では目的が達せられません。

(1) if ( 0 <= x < 10 )      { ... }   // NG
(2) if ( 0 <= x && x < 10 ) { ... }   // OK
(3) if ( x >= 0 && x < 10 ) { ... }   // OK

(1) は、エラーにはなりませんが違う意味になってしまいます。正しくは(2)や(3)のように、2つの条件を && で結びます。一般に、2つの条件が同時に成りたって欲しければ &&、どちらか片方が成り立てばよいのであれば || をはさんで書き並べます。

(1) の表現には、gcc だと警告レベルを上げると検出してくれます(→Cygwinでデバッグ/不可解な動作)。Java ではエラーになります。

ちなみに (1) の式は ((0<=x) < 10) と評価されます。「(0<=x)」の比較結果は(違和感があると思いますが)0 か 1 の数値で表現されます(→比較演算)。それが 10 より小さいかが評価されるので、最終的には「常に成り立つ」ということになります。

(2) と (3) はどちらも同じ意味ですが、数直線を思い描く人はにとっては不等号の向きが揃った (2) の表現のほうが理解しやすいでしょう。変数が左にあるほうが落ち着く人には (3) が好まれるでしょう。

さて、上の条件の否定をどう書くのか考えてみます。いろいろなバリエーションが考えられます。

    if (  0 <= x  &&  x < 10  ) { ... } // 否定する前
(a) if (!(0 <= x  &&  x < 10) ) { ... } // 単純に ! をつけて否定にした
(b) if (!(0 <= x) ||!(x < 10) ) { ... } // ! を分配して && と || を入れ替え
(c) if (  0 > x   ||  x >= 10 ) { ... } // ! をやめて不等号を入れ替え
(d) if (  x < 0   ||  x >= 10 ) { ... } // 左に変数が来るように
(e) if (  x < 0   ||  10 <= x ) { ... } // 不等号の向きを揃えた

どれも同じ意味ですが、よく使うのは (a), (d), (e) あたりでしょうか。

余談ですが、"<=" と "=<" のどちらか迷う人が多いですが、正しいのは片方だけです。覚え方は「+= も *= も <= も >= も、すべて = が後ろ」です。

C言語の2文字の演算子は = がいつでも後ろですが、Perl では残念なこと(?)に "=~" という演算子があります。

比較演算

数学で考えれば a+b と a<b は計算の質が異なります。a+b はいつでも計算できます。反面 a<b は計算するものではなく、条件として扱うものです。

ところが、C言語をはじめとする多くのコンピュータ言語では、a+b と a<b のどちらも計算式であって、計算結果としての値を持ちます。C言語でも数学でも、a+b は当然のようにaとbの和です。そしてC言語では、a<b は成り立てば int 型の 1、成り立たなければ int 型の 0 の値になります。変数の a, b の型によらず、比較結果は必ず int 型になります。

1 とか 0 とかの値は、言語によって若干のバリエーションがあります。太古の BASIC インタプリタでは整数型の -1 と 0 でした。Java では boolean 型の true と false という値になります。

つまり、c = a+b とすれば c に a+b の値を代入できるように、c = a<b のような代入も可能です。従って、次のような書き換えもできてしまいます(が、あまり使いません)。

if ( a < b ) { ... }    // 短縮(?)版
int cond = (a < b);     // 分割版
if ( cond )  { ... }

次はよく使う例です。無限ループを

while ( 0 == 0 ) { ... }

と書くことがありますが、0==0 の演算結果は、成り立つので 1 となります。ですから、

while ( 1 )      { ... }

と表記するのと同じ意味になります。

条件式は 0 以外は成立とみなされるので、while (2) {...} でも無限ループです。

x が 0 かどうかを判定する関数 is_zero(x) は次のような2通りの書き方が可能です。

int is_zero(int x) {
    if (x == 0) return 1;
    else        return 0;
}
int is_zero(int x) {   // 短縮版
    return (x == 0);
}

このような関数を呼び出す上では注意が必要です。常套句があって、次のように書きます。

if ( is_zero(a) ) { ... }

if の条件式の中で比較を行っていないことに注目して下さい。比較は関数(短縮版)の中ですませているので、if の条件式では 1 と比較することすらしないのが流儀です。この流儀を用いるのはどういう場面かというと、関数名・変数名が is で始まっている時です。プログラムが英語の文章のように読めるでしょう、if a is zero, then ... のように。

否定の条件は次のように書きます。

if ( !is_zero(a) ) { ... }

これは if a is not zero, then ... のように理解します。

C言語の標準関数に isalpha(c) のようなものがあります。不成立なら 0 を返しますが、成立した場合に 1 ではなく、64 のような値を返すことがよくあります。ですから 1 と比較すると間違ったプログラムになってしまいます。

C 言語ではこのような関数・変数に int 型を用いるため、バラツキに対する注意が必要です。(と言っても、比較しないという流儀を守ればよいだけです。)C++ や最新の C99 には bool 型があるのですが、やはり内部的な値にバラツキがあるので、同じ注意が必要です。安心なのは Java の boolean 型で、true と false の2通りの値に制限されています。(が、やはり比較しないのが流儀です。)


トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2020-11-09 (月) 12:36:36