トラック6・🔀制御構造

条件式の罠——= と == の悲劇

読む目安 約10分・ゴール:代入 = と比較 == の違いを説明でき、条件式で取り違えたときに何が起きるかを見抜ける

アプリでこのトピックを学ぶ(無料・登録不要)

このトピックの要点

算数で習った「=」は「等しい」という意味でした。C言語の = は「入れる」です。この1文字の食い違いが、C言語で最も有名な事故を生みます。

仕組みから見ていきましょう。

a = 5 は代入です。そして重要なのは、代入した結果、式全体が「代入した値」を持つという点。つまり a = 5 という式そのものの値は 5 です。

a == 5 は比較です。式の値は真か偽になります。

ということは、if (a = 5) はこう読まれます。「a に 5 を入れて、その結果の 5 を条件として見る」。5 は 0 以外なので——常に真です。しかもコンパイルは通ります。

#include <stdio.h>

int main(void) {
    int stock = 0;

    if (stock = 5) {          // 本当は stock == 5 と書きたかった
        printf("在庫は5個です\n");
    } else {
        printf("在庫は5個ではありません\n");
    }
    printf("stock = %d\n", stock);
    return 0;
}

在庫は 0 のはずなのに、「在庫は5個です」「stock = 5」と表示されます。判定するだけのつもりが、変数の中身まで書き換えてしまったのです。あとの行で stock を使う処理があれば、そこも全部おかしくなります。バグの発生地点と症状が出る場所が離れるので、原因にたどり着くのに時間がかかる——これが「悲劇」と呼ばれるゆえんです。

師範= は「入れる」、== は「同じか?」。この1文字の差で、判定が代入にすり替わる。世界中のC書きが一度は通る道じゃ

対策は3つあります。

1. コンパイラの警告を有効にする。 gcc や clang なら -Wall を付けるだけで「代入を条件として使っていますよ」と教えてくれます。人間の注意力より、機械のチェックのほうが確実です。

2. 定数を左に置く(ヨーダ記法)。 if (5 == stock) と書いておけば、うっかり = を1つ落として if (5 = stock) としてもコンパイルエラーになります。定数には代入できないからです。読みにくいという意見もありますが、確実に事故を1種類つぶせます。

#include <stdio.h>

int main(void) {
    int stock = 0;

    if (5 == stock) {         // 書き間違えたら即コンパイルエラー
        printf("在庫は5個です\n");
    } else {
        printf("在庫は5個ではありません\n");
    }
    return 0;
}

3. 読み上げる。 if (stock == 5) を「もし stock が 5 と等しいなら」と口に出す。= が1つだと「もし stock に 5 を入れるなら」となり、日本語として明らかにおかしいので気づけます。

最後に、bool型との比較について。真偽値を == true と比べたくなりますが、その必要はありません。

    bool isOpen = true;

    if (isOpen == true) { /* … */ }   // 動くが回りくどい
    if (isOpen) { /* … */ }           // こう書けばよい

条件式はもともと真偽を見る場所なので、bool 変数はそのまま置けます。比較を書かなければ、= と == を間違えようもありません。

まとめます。= は代入、== は比較。代入式の値は代入した値そのもの。if (x = 5) は常に真になり、おまけに x を壊す。警告を有効にして、bool はそのまま書く。

アプリでこのトピックを学ぶ(無料・登録不要)

確認問題

確認問題 1

int a = 0; と宣言したあと、if (a = 3) { printf("yes\n"); } を実行しました。何が起きますか。

  1. コンパイルエラーになる
  2. 何も表示されず、a は 0 のまま
  3. yes と表示され、a は 3 になる
  4. yes と表示されるが、a は 0 のまま
答えを見る

正解:C(yes と表示され、a は 3 になる)

a = 3 は代入式で、その式の値は「代入した値」つまり 3 です。Cでは0以外がすべて真なので条件は成立し、yes が表示されます。同時に a は 3 に書き換わります。コンパイルは通ってしまう(警告は出ることがある)ので、コンパイラに怒られないまま動きだけがおかしくなる、という一番やっかいな形のバグです。

確認問題 2

在庫がちょうど5個かどうかを判定するつもりのコードです。stock は 0 なのに「在庫は5個です」と表示され、さらに stock の値まで変わってしまいました。バグのある行はどこですか。

1#include <stdio.h>
2
3int main(void) {
4    int stock = 0;
5
6    if (stock = 5) {
7        printf("在庫は5個です\n");
8    } else {
9        printf("在庫は5個ではありません\n");
10    }
11    printf("stock = %d\n", stock);
12    return 0;
13}
答えを見る

バグのある行:6行目

6行目の if (stock = 5) は比較ではなく代入です。stock に 5 を入れ、その式の値(5)がそのまま条件になるので、必ず真になります。おまけに stock の中身が 0 から 5 に書き換わります。正しくは if (stock == 5) です。gcc や clang で -Wall を付けておくと「代入を条件として使っている」と警告してくれます。

= は「入れる」、== は「同じか?」。C言語で最も有名な取り違えです。代入式は「代入した値」を式の値として持つため、if (x = 5) は if (5) と同じ=常に真になります。判定するだけのつもりが変数を壊すので、症状が出る場所と原因の場所が離れやすく、発見が遅れます。

「制御構造」の目次

  1. 流れを変える——制御構造の全体像
  2. if文——もしも、の分かれ道
  3. else if / else の分かれ道設計
  4. 条件式の罠——= と == の悲劇
  5. 論理演算子と短絡評価
  6. switch文とfallthrough
  7. for文——回数が決まった繰り返し
  8. while文——条件が決める繰り返し
  9. do-while文と入力チェック
  10. breakとcontinueの使いどころ
  11. ループのネストと九九表
  12. 無限ループとの付き合い方

もっと先へ:ポインタ・メモリ・ファイル入出力・セキュアコーディングを含む全26トラックと、段位検定・模試のフルセットは完全版に収録しています。

完全版の販売ページは準備中です。

ほかのトラック

📚 姉妹教材:手を動かして覚える Linux 教材 — Linuxとインフラの仕組みを地図で学ぶ

Web版