C言語基礎|空文に注意しよう

はじめに:空文は「動かないのに動いた気がする」落とし穴

C言語でループを書いているとき、たまに「ちゃんと書いたはずなのに、出力が1回しか出ない…」みたいな事故が起きます。
その代表が 空文(null statement) です。

空文は、見た目が地味すぎて気づきにくいのに、動作への影響はかなり強め。
ここでは、ありがちなミスを別のプログラム例で体験しつつ、「なぜそうなるのか」「どう防ぐのか」をしっかり押さえます。

サンプル:回数分「★」を出したいのに、1回しか出ない(空文の例)

本来は、入力した回数だけ ★ を表示したいとします。
でも、次のコードを見ると「n回表示される」と思っちゃいませんか?

for (int i = 1; i <= n; i++);
    putchar('★');

ところが実際は、n が何であっても ★は1個しか表示されません
原因は for (...) の直後にあるセミコロン ; です。

なぜ1回しか表示されないのか:for文の「本体」は直後の1文だけ

C言語の for文は、( ) の直後にある「1文」がループ本体になります。
つまり、次のように解釈されます。

for (int i = 1; i <= n; i++)  // ループは回っている
    ;                         // でも本体が空文(何もしない)
putchar('★');                  // ループが終わったあとに1回だけ実行

このコードの流れ

for が n 回繰り返す
  └ 本体は ; (何もしない)
for が終わる
  └ putchar('★') が 1 回だけ実行される

正しい書き方:本体をブロックにして「意図」を固定する

ループ本体を確実にまとめるなら、複合文(ブロック)にするのが安全です。

for (int i = 1; i <= n; i++) {
    putchar('★');
}

間違いと正しい例の比較

書き方見た目の意図実際の動作
for (...) ; putchar(...)n回出したいputchar は1回だけ
for (...) putchar(...)n回出したいn回出る
for (...) { putchar(...); }n回出したい(明確)n回出る(安全)

用語整理:式文と空文(null statement)

C言語では、式の後ろにセミコロンを付けると 式文(expression statement) になります。

例:

x = 10;
i++;
sum += value;

そして、式がないセミコロンだけの文も、文として成立します。これが 空文 です。

;

式文と空文

種類何をする?
式文i++;式を評価し、副作用があれば反映される。
空文;何もしない(でも文として扱われる)

どこで起きやすい?for文だけじゃなく while文でも同じ

for文だけじゃなく、while文でも同じ事故が起きます。

while (x < 10);
    x++;   // これ、ループの中じゃない

この場合も、while の直後の ; がループ本体になってしまい、x++ はループ終了後に1回だけ実行されます。
さらに怖いのは、条件がずっと真なら 無限ループ になって止まらないこともある点です。

while の空文ミス

while (条件) ;
  └ 条件が真の間ずっと「何もしないループ」
ループ終了後に x++ を 1 回だけ実行

どう防ぐ?空文ミスを減らすコツ

防止策のまとめ

対策効果
ループ本体は基本 { } で書く余計な ; が入っても気づきやすい。
インデントを信じすぎないC言語はインデントで意味が変わらない。
ループ直後に ; を置かない癖をつけるミスの根を断つ。
コンパイラ警告を有効化するこういうミスを警告してくれることが多い。

繰返し文(iteration statement)というまとめ呼び

ここまで出てきた、do文・while文・for文は、全部「繰返し文」と呼ばれます。
つまり「プログラムの流れを繰り返すための文」の仲間です。

繰返し文の種類

種類特徴
do-while文少なくとも1回は本体が動く(後判定)
while文条件が真の間だけ動く(前判定)
for文回数・条件・更新をまとめて書ける(前判定)

演習問題

演習4-16:全約数を表示し、最後に「偶数の約数の個数」も表示せよ

整数 n を読み込み、n の約数をすべて表示する。
さらに、表示が終わった後に 偶数の約数が何個あったか を表示せよ。

実行例

整数値:12
1
2
3
4
6
12
偶数の約数は4個です。

解答例

#include <stdio.h>

int main(void)
{
    int n;
    printf("整数値:");
    scanf("%d", &n);

    int even_cnt = 0;

    for (int i = 1; i <= n; i++) {
        if (n % i == 0) {
            printf("%d\n", i);
            if (i % 2 == 0)
                even_cnt++;
        }
    }

    printf("偶数の約数は%d個です。\n", even_cnt);
    return 0;
}