トラック6・🔀制御構造

無限ループとの付き合い方

読む目安 約11分・ゴール:無限ループの原因を3つの型で診断でき、意図的な無限ループを出口つきで安全に書ける

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

このトピックの要点

実行したら、画面が固まって返ってこない。カーソルだけが点滅している。初めてこれに出くわすと「壊した」と思うものですが、たいていは無限ループです。壊れてはいません。怖がるより、原因の型を覚えたほうが早く片付きます。

まず止め方から。実行中のコンソールで Ctrl + C を押せば中断できます。これを知っているだけで恐怖は半分になります。

無限ループの原因は、ほぼ3つの型に収まります。

型1: 更新を忘れた。 whileの中で、条件に使っている変数を動かしていない。

#include <stdio.h>

int main(void) {
    int i = 0;

    while (i < 5) {
        printf("%d ", i);
        // i++;   ← これが無いと 0 が出続ける
    }
    return 0;
}

型2: 更新はしているが、条件に近づかない。 i-- と書くつもりが i++ になっている、i = i + 1 のつもりが i + 1 だけ(計算しても代入していない)、あるいは条件がそもそも絶対に偽にならない(while (x != 3) に対して x が 2 ずつ増えていて 3 を飛び越す、など)。

型3: continueが更新を飛ばした。 前のトピックで見た while + continue の地雷です。

診断のコツは、疑わしいループの先頭に printf を1行入れて、条件に使っている変数の値を毎周表示させること。数字が動いていなければ型1か型3、動いているのに条件に近づかないなら型2です。原因が絞れれば、直すのは一瞬です。

ビット固まっても壊れてないよ。Ctrl+C で止めて、条件の変数がどう動いてるか表示させてみよう

ここまで悪者扱いしてきましたが、わざと終わらせないループもあります。ゲームのメインループ、組み込み機器の制御、サーバーの待ち受け——これらは電源が切れるまで回り続けるのが正しい姿です。

#include <stdio.h>

int main(void) {
    int count = 0;

    while (true) {              // C23 なら true をそのまま書ける
        count++;
        if (count >= 3) {
            printf("3回まわったので終わります\n");
            break;              // 出口は必ず用意する
        }
    }
    return 0;
}

while (true) や for (;;) が定番の書き方です。大事なのは、出口を必ず1つ用意すること。「条件で終わらせない」と決めたなら、代わりに break で終わらせる責任が生じます。出口の無い while (true) は、ただのバグです。

最後に、このトラックの総まとめとして FizzBuzz を書きましょう。1から15まで表示し、3の倍数なら Fizz、5の倍数なら Buzz、両方の倍数なら FizzBuzz にします。

#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 15; i++) {
        if (i % 15 == 0) {
            printf("FizzBuzz\n");
        } else if (i % 3 == 0) {
            printf("Fizz\n");
        } else if (i % 5 == 0) {
            printf("Buzz\n");
        } else {
            printf("%d\n", i);
        }
    }
    return 0;
}

forで回数を決めて回し、if / else ifを狭い条件から並べ、剰余で判定する。このトラックで学んだことが全部入っています。もし i % 3 == 0 を先頭に置いたら、15 は Fizz に吸い込まれて FizzBuzz は一度も表示されません。t06-03 で見た「順番の罠」が、ここでも顔を出すわけです。

ループを書く前のチェックを3つ、置いていきます。この条件は、どの行で偽になる? 条件に使う変数は毎周きちんと動いている? 最悪でも何周で終わる? 3つ目に答えられないループには、安全弁のカウンタを入れておくと安心です。

師範無限ループは失敗ではない。出口を決め忘れただけじゃ。止めて、条件をもう一度読め。それだけのことよ

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

確認問題

確認問題 1

「0〜4のうち 2 だけを飛ばして表示する」つもりのコードです。ところが実行すると 0 1 まで出たまま画面が止まり、いつまでも返ってきません。原因になっている行はどこですか。

1#include <stdio.h>
2
3int main(void) {
4    int i = 0;
5
6    while (i < 5) {
7        if (i == 2) {
8            continue;
9        }
10        printf("%d ", i);
11        i++;
12    }
13    printf("\n");
14    return 0;
15}
答えを見る

バグのある行:8行目

8行目の continue; が、11行目の i++; を飛ばしてしまいます。i が 2 になった瞬間、i は永遠に 2 のまま条件 i < 5 が真であり続ける=無限ループです。直し方は2つ。continue の前に i++; を済ませておく(if (i == 2) { i++; continue; })か、素直に for 文にして更新式に i++ を持たせるかです。for なら更新式は本体の外にあるので、continue でも必ず実行されます。

continue は「本体の残りを飛ばして次の周回へ」。for の更新式は本体の外にあるので飛ばされませんが、while では更新処理も本体の一部なので、continue に置き去りにされます。無限ループの典型的な原因のひとつです。実行が止まらなくなったら Ctrl + C で中断できます。固まっても壊れてはいません。

確認問題 2

アイに FizzBuzz(1〜15を表示し、3の倍数は Fizz、5の倍数は Buzz、両方の倍数は FizzBuzz)を頼みました。アイの答案にバグはあるでしょうか。

1#include <stdio.h>
2
3int main(void) {
4    for (int i = 1; i <= 15; i++) {
5        if (i % 15 == 0) {
6            printf("FizzBuzz\n");
7        } else if (i % 3 == 0) {
8            printf("Fizz\n");
9        } else if (i % 5 == 0) {
10            printf("Buzz\n");
11        } else {
12            printf("%d\n", i);
13        }
14    }
15    return 0;
16}
答えを見る

判定:バグなし

今回のアイは正解です。決め手は判定の順番で、最も条件の厳しい i % 15 == 0(3でも5でも割り切れる)を先頭に置いています。もし i % 3 == 0 を先に書いていたら、15 は Fizz に吸い込まれて FizzBuzz が一度も表示されませんでした(t06-03 の「狭い条件から並べる」がそのまま効いています)。ループの範囲 i = 1; i <= 15 も、1から15までちょうど15回で正しい。バグが無いときに「無い」と言い切れることも、AIの答案を検証する大事な力です。疑うことと、けなすことは違います。

「制御構造」の目次

  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版