🇯🇵 日本語 | 🇺🇸 English
広告スペース

C言語の真偽値 ― bool と truthy/falsy

Cでは0=偽、非0=真。シンプルだが罠も多い。C99以降のbool型と合わせて正しい書き方を身につける。

K&R時代 → C99 の bool

C言語はもともと真偽値専用の型を持たない言語でした。代わりに「0=偽、非0=真」という約束を全ての条件式で使っていました。

昔ながらの書き方 (C89以前)

int done = 0;         // 0 = 偽
while (!done) {
    // ...
    if (condition) done = 1;    // 1 = 真
}

C99 以降の書き方

#include <stdbool.h>   // bool, true, false が使える

bool done = false;
while (!done) {
    if (condition) done = true;
}
内部実装: bool は実は _Bool 型のマクロ、true/false は単なる 1/0 のマクロ。_Bool は0か1しか保持しない特殊な型で、「非0を代入すると1になる」特徴がある。
_Bool b = 42;     // _Bool は 0 と 1 しか保持しないので 1 になる
printf("%d\n", b);  // → 1

int i = 42;       // 普通のintは42のまま
printf("%d\n", i);  // → 42

真偽の基本ルール

条件式(ifwhilefor?: など)では次のルールが使われます。
0 → 偽 (false)
0以外 → 真 (true)
NULL → 偽(ポインタの0)
0.0 → 偽(浮動小数点の0)
if (0)   { // 実行されない }
if (-1)  { // 実行される(非0だから真) }
if (42)  { // 実行される }
if (0.0) { // 実行されない }

char *p = NULL;
if (p)    { // 実行されない (NULLは0) }
「真」の値はいろいろある: -1も42も「真」だが、それらを区別する必要がある場合は明示的に比較すべき(「1に等しい」は「真である」とは違う)。

比較演算子の戻り値

==!=<><=>= の結果は必ず 0 か 1 の int です。true/false ではなく int であることに注意。
int a = 5, b = 3;
int r = (a > b);   // r は 1 (int)
printf("%d\n", r);  // → 1

int s = (a < b);
printf("%d\n", s);  // → 0

// カウンタに足すテクニック
int count = 0;
for (int i = 0; i < n; i++) {
    count += (a[i] > 0);   // 正の要素の個数を数える
}

ポインタと浮動小数点の真偽

ポインタ: NULL か否か

char *p = malloc(100);
if (p) {
    // NULLでない → 確保成功
    free(p);
}

// 次の2つは同じ意味(好みの問題)
if (p)          { /* ... */ }
if (p != NULL) { /* ... */ }
どちら派が多い? K&R的な慣習は if (p)、明示派は if (p != NULL)。ほぼ宗教論争だが、可読性重視のコードベースでは NULL 比較を推奨することが多い。

浮動小数点: == 0.0 には要注意

❌ 危険: 浮動小数点の==

double x = 0.1 + 0.2;
if (x == 0.3) {       // falseになる!
    // 実際の x ≈ 0.30000000000000004
}

✅ 推奨: イプシロン比較

#include <math.h>

double x = 0.1 + 0.2;
if (fabs(x - 0.3) < 1e-9) {
    // 許容誤差内で一致
}
完全な 0.0 の判定だけは if (x == 0.0) で問題ない(0は正確に表現できる)。ただし計算結果として0を期待するなら通常イプシロン比較が安全。

よくあるバグ

バグ1: if (x == true) が危険な理由

int x = 2;           // 2 は「真」だが 1 ではない

if (x == true) {     // NG: true は 1。2 == 1 は false
    printf("入らない\n");
}

if (x) {            // OK: 非0なので真として入る
    printf("入る\n");
}
ルール: 真偽値として使うなら比較せずそのまま使うif (x) / if (!x)。どうしても正規化したいなら後述の !!x イディオムを使う。

バグ2: === の取り違え

❌ 代入してしまう

if (x = 5) {
    // これは「xに5を代入、結果(5)を真と判定」
    // 常に真になってしまう
}

✅ 比較する

if (x == 5) {
    // xが5と等しいかの比較
}
予防策: -Wall を付けると if (x = 5) で警告が出る。定数を左に書くテクニック (if (5 == x)) で typo を検出しやすくする派もいる(Yoda conditions)。

バグ3: ! の優先順位

if (!x && y)        // (!x) && y  つまり「x が偽 かつ y が真」
if (!(x && y))      // x と y のどちらかが偽
// 意味が違う! 括弧で意図を明示

バグ4: ビット演算子と論理演算子の混同

if (a & b)   // ビットAND: a と b の両方のビットが立っている位置がある
if (a && b)  // 論理AND: a も b も真

// 例: a = 4 (100), b = 3 (011)
// a & b  → 0 (共通ビットなし) → 偽
// a && b → 真 (両方とも非0)

イディオム: !!x で 0/1 に正規化

! を2回適用すると、任意の値を 0 か 1 に揃えることができます。
int x = 42;
int normalized = !!x;   // 42 → !42=0 → !0=1  → 1

int y = 0;
int n2 = !!y;           // 0 → !0=1 → !1=0  → 0

使いどころ

より明示的: bool b = (x != 0); の方が読みやすい派もいる。_Bool への代入なら自動で 0/1 になるので _Bool b = x; でも同じ。好みとチームのスタイルに合わせる。

short-circuit 評価

&&||左から右へ評価し、結果が確定した時点で右側を評価しない(短絡評価、short-circuit)。

&& の挙動

if (p != NULL && p->value > 0) {
    // p が NULL なら p->value を評価しない(NULL参照回避)
}

|| の挙動

if (x == 0 || y / x > 10) {
    // x が 0 なら y/x を評価しない(0除算回避)
}
順序が重要: x == 0 || y / x > 10y / x > 10 || x == 0意味が違う。後者は 0除算を起こしうる。ガード条件は必ず左側に書く

副作用を使うイディオム

// 関数呼び出しをガード条件付きで実行
if (mode_debug && log_debug(msg)) {
    // mode_debug が偽なら log_debug は呼ばれない
}

// デフォルト値の代入(一部のコードベースで使う)
int x = get_value() || 10;  // ただしCでは結果は 0か1、10にはならない!
注意: JavaScript の || と違い、Cの || は値そのものを返さない(結果は常に0か1)。JSのようなデフォルト値代入には使えない

チャレンジ課題

課題1: 予想 → 実行
以下の printf は何を出力するか予想してから実行して確かめよ。
printf("%d %d %d %d\n", !0, !1, !!5, !!-3);
課題2: if (x == true) の罠
int x = 2; のとき、if (x == true)if (x) の分岐結果が異なるコードを書き、違いを printf で確認せよ。
課題3: 浮動小数点の等値性
0.1 + 0.2 == 0.3 が false になることを確認し、イプシロン比較で true になるよう書き直せ。
課題4: 短絡評価でNULL回避
文字列ポインタ s を受け取り、「NULLでなく、かつ先頭が 'A' なら 1 を返す」関数を書け。NULL参照でクラッシュしないよう && の順序に注意。
課題5: 比較結果をカウント
int 配列の要素のうち、値が0以上のものの個数を、if文を使わず比較演算の戻り値を加算するループで数えよ。