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

🔦 第12回 プログラムが分からない? printf で変数を追おう

コードを眺めて悩む時間を、変数の値を「見る」時間に変える。初学者のためのいちばん実用的なデバッグ入門。

📖 このページで覚えること
✅ 最低限ここだけ覚える
  • 分からない = 変数の中身を見ていない
  • 迷ったら printf("変数名=%d\n", x);
  • ループの中、関数の入口・出口でとにかく出す
⭐ 余裕があれば読む
  • デバッグ出力は[DEBUG]プレフィックスで識別
  • printfのバッファリングに注意(改行 \n
  • 慣れたら gdb / lldb / IDE のデバッガへ

なぜ printf デバッグが最強か ― 初学者の 9 割はここで詰まる

プログラミングで伸び悩む人の多くに共通する症状があります:
コードを眺めて頭の中で動かそうとする。そして「わからない…」と固まる。
🔴 よくある間違ったアプローチ
致命的な誤り: 人間の脳は5つも6つも変数を同時に追いかけられません。しかもループやif分岐が入ると即座にパンクします。目で追う=諦めと思ってください。
✅ 正しいアプローチ
コンピュータに変数の値を「見せてもらう」。それだけ。
// 怪しい場所に printf を山ほど入れる
printf("[DEBUG] i=%d, sum=%d\n", i, sum);
これを10箇所やっても3分で終わります。 その3分で「頭の中で動かす1時間」と同じ情報が手に入る。これがプロと初学者の最大の違いです。
🎯 プロのプログラマも本質は同じ: 熟練者は IDE のデバッガや gdb を使いますが、やっていることは「実行中の変数を見る」で同じ。最初は printf で十分です。

どこに printf を入れるか ― 3つの黄金パターン

パターン① ループの中 ― 1周ごとに変数を出す
for (int i = 0; i < 5; i++) {
    sum = sum + arr[i];
    printf("[DEBUG] i=%d, arr[i]=%d, sum=%d\n", i, arr[i], sum);  // ← これ
}
ループは想定外の挙動の温床。1周ごとの状態を出力すれば、どの回で値が狂ったか一発で分かります。
パターン② 関数の入口と出口
int calc(int a, int b) {
    printf("[DEBUG] calc 入口: a=%d, b=%d\n", a, b);
    int result = a * 2 + b;
    printf("[DEBUG] calc 出口: result=%d\n", result);
    return result;
}
関数に何が渡ってきて何が返るかの2点を見るだけで、バグが関数の中か外かが即座に切り分けできる。
パターン③ if文の分岐の前後
printf("[DEBUG] 判定前: score=%d\n", score);
if (score >= 60) {
    printf("[DEBUG] 合格ルート\n");
    printf("合格\n");
} else {
    printf("[DEBUG] 不合格ルート\n");
    printf("不合格\n");
}
「なぜか条件分岐が思った通りに入らない」の99%は条件式の値が想定と違う。判定直前に変数の中身を見るだけで解決。
💡 迷ったら: 「思ったのと違う値になるかもしれない全ての変数」に printf を入れる。やりすぎでOK。後で消せばいい。

Before / After 実例 ― 同じバグで苦しみ方が変わる

「10 までの合計を計算するはずなのに結果が 45 にならない」という典型的なバグ。2つのアプローチを比較します。
😩 Before: printfなし
int sum = 0;
for (int i = 1; i < 10; i++) {
    sum = sum + i;
}
printf("合計 = %d\n", sum);
// 出力: 合計 = 45
// 「あれ? 55 のはずじゃ?」
// ...コードを10分間眺める...
// ...まだ分からない...
詰まる時間: 10〜60分
😌 After: printf追加
int sum = 0;
for (int i = 1; i < 10; i++) {
    sum = sum + i;
    printf("[D] i=%d sum=%d\n", i, sum);
}
printf("合計 = %d\n", sum);
// 出力:
// [D] i=1 sum=1
// [D] i=2 sum=3
// ...
// [D] i=9 sum=45    ← i が 10 まで行ってない!
// 合計 = 45
詰まる時間: 30秒 → i<10 を i<=10 に修正
体感の違い: printf ありは「i の最大が 9 だ」という事実を見せてくれます。printf なしでは i < 10i <= 10 の違いが頭の中で曖昧なまま、バグが消えない。

型別 printf 書式一覧 ― コピペで使える

型が違うと書式指定子も違います。迷ったらここを見ればOK。
書式出力例
int%dprintf("%d", x)42
long%ldprintf("%ld", y)1234567890
float / double%fprintf("%.2f", z)3.14
char%cprintf("%c", c)A
char[] / 文字列%sprintf("%s", s)Hello
ポインタ/アドレス%pprintf("%p", p)0x7ffee...
16進数%xprintf("%x", n)ff
💡 最速チートテンプレ:
printf("[D] var1=%d var2=%d\n", var1, var2); を何度もコピペして変数名だけ書き換える。改行 \n を忘れないこと(次項参照)。

落とし穴と後片付け

🔴 落とし穴① 改行 \n がないとすぐ表示されない
printf は効率のため出力をバッファに貯めます。改行コード \n が来ると初めて画面に出す(行バッファリング)。
printf("[D] ここまで来た");   // ← \nなし → まだ画面に出ない!
// クラッシュして原因調査しても「[D] ここまで来た」が出ないので
// 「ここまで来ていない」と勘違いしてしまう。
対策: デバッグ用 printf は常に \n を付ける。もしくは fflush(stdout); を直後に呼ぶ。
⚠️ 落とし穴② 型を間違えると嘘の値が出る
double x = 3.14;
printf("%d\n", x);    // ❌ %d は int 用、結果は予測不能なゴミ値
対策: 前項の書式表を確認。コンパイル時に警告で気づけるよう -Wall で warn を有効に。
🧹 後片付け ― コミット前に削除
バグが直ったらデバッグ用 printf は全部消すこと。残したまま提出/コミットすると読みにくいし、本番で出力が大量に出て問題になります。
💡 見つけやすくするコツ: デバッグ用は必ず [DEBUG][D] といった識別子付きで書く。後で [D] で全文検索すれば一瞬で全部見つかります。
🚀 次のステップ: printf デバッグに慣れたら、第19回 デバッグの技法gdb実習ガイド へ。対話的に変数を見ながら止められるようになると、デバッグ効率がさらに2〜3倍上がります。
広告スペース

関連する講座

入門編
第4回 printf・scanf
printf の基本と書式指定子。こちらで一覧を復習。
繰り返し・配列・文字列
第19回 デバッグの技法
さらに本格的なデバッグ手法と、よくあるバグの見つけ方。
応用トピック
gdb実習ガイド
printf の次のステップ。対話的に変数を見る強力ツール。
← 前の講座
第11回 比較・論理演算子
次の講座 →
第14回 条件分岐(if文)