とらりもんHOME  Index  Search  Changes  Login

C言語入門8. デバッグのやりかた

バグとは

 プログラミングは難しいもので, 小さなプログラムでも, 組んですぐに完璧に動くことは稀である。その主な理由は,

  • 人間は間違うものだから。
  • C言語は特に間違いに容赦ない言語だから。

である。実際, 我々が組むC言語プログラムは, 間違いのためにうまくコンパイルできなかったり, たとえコンパイルはできても, 望んだようには動かなかったりするものである。

 そのような間違いをコンピュータの業界ではバグ (bug) と呼ぶ。「バグ」はもともと「虫」という意味である。機械の中に我々の邪魔をする害虫がひそんでいるイメージである(その虫を作り出したのは我々自身なのだが)。そしてその虫の退治, つまりプログラムの中の間違いの修正作業を, コンピュータの業界ではデバッグ (debug) と呼ぶ。

 バグの原因は様々だが, 初心者がよくやらかすのは以下のようなものである:

  • スペルミス。例:printfをprontfと書いてしまう。
  • 記号の打ち間違い。例: ドット"."とコンマ","の誤用。
  • まぎらわしいアルファベット記号の打ち間違い。例: l(小文字のエル)と1(数字のイチ)とI(大文字のアイ)の間違いや, 0(数字の零)とO(大文字のオー)の間違い。

英語が不得意な人(特にスペリングが怪しい人)はこれらのバグをよく作る(悔しいがコンピュータの言語のほとんどは英語をベースにしている。英語の基礎学力はこういうところにも効いてくるのだ)。しかしこれらのバグはコンパイルのときにエラーメッセージで指摘されるから, 発見も直すのも意外と簡単である。

  • 必要な手続きの欠落。例:宣言せずに変数を使う。for文の"{"を閉じるための"}"を書き忘れる。文の最後のセミコロン";"を忘れる。

これも単純な文法ミスなので, コンパイルのときにエラーメッセージで指摘されるから, 直すのも簡単である。しかし, 文法が正しくても,

  • 許されない処理。例:配列変数xを, int x[10];と宣言したのにx[10]にアクセスする(x[0]からx[9]までしか許されない)。
  • 不適切な変数型。例:小数計算させたいのに整数変数で行う。
  • 根本的に処理の考え方(アルゴリズム)が間違っている。
  • 必要なライブラリを読み込んでいない。(#include <math.h>や#include <stdlib.h>を宣言していない等)

などに起因するバグもある。これらは, コンパイルでは見つからないことが多く, 実行して"segmentation fault error"というエラーメッセージを受けたり, あるいは間違った結果や, いつまでたっても終わらぬ処理(無限ループという)に陥ることでバグに気づく。

しかし本当に怖いのは, エラーメッセージが出なくて, 処理もちゃんと終わり, 結果もそれらしいのに, 実は間違った処理をしているというバグである。これは発見が大変に難しい。バグに気づかずに処理を進めて結果を出し, 論文にして出版してから何年も経ってバグに気付くということもある(もしくは永遠に気づかれないままのバグすらありえる)。そういうバグの対策は, 研究者・技術者としての全般的なレベルアップしかない。基礎学力, 思考力, 観察力などを地道に向上させ, 経験を積んでいくしかないのである。

デバッグのテクニック

デバッグは経験がものをいう技術である。とはいえ, 経験だけに任せるのも学習効率が悪いので, ここでデバッグのテクニックとしていくつか代表的なものを挙げておく。

エラーメッセージをよく読み, 理解する

 エラーメッセージには, エラー(バグ)に関する情報が詰まっている。エラーメッセージをよく読み, その意味を理解することで, 原因を突き止められることが多い。

 見慣れないエラーメッセージが出てきて, しかもその意味がわからなければ, そのメッセージをGoogleなどに放りこんで検索をかけてみよう。どのような場合にそのようなエラーが出るのかという情報が得られるかもしれないし, 君と全く同じバグに遭遇して解決した人の経験が出ているかもしれない。

 エラーメッセージには, エラーが発生位置の行番号が表示されることが多い。ソースコードのその部分を修正するには, viのコマンドモードで,

:5

のようにコロンに続けて行番号(この例では5行め)を入れてENTERを押せば, その行へすぐにジャンプできる。

怪しい部分の前でexitするか, 怪しい部分をコメントアウトして, 怪しい箇所を絞り込む

 segmentation faultなどのエラーは不具合箇所を教えてくれない。このような場合, 怪しい箇所を自分で適当に目星をつけて, その前に

exit(0);

という関数を入れて実行してみよう。この関数はその箇所でプログラムを強制的に終了する。したがって, exit(0);を入れたらエラーが出ずに終了する場合は, 問題箇所はexit(0);よりも後に存在するはずだ。そうやって, 少しずつexit(0);を後にずらしていくのだ。すると, exit(0);がある場所まで来たときにエラーが再発するだろう。そこに不具合が潜んでいる可能性が高い。

 また, あやしい箇所を, //などでコメントアウトするのも有用である。コメントアウトすることによってエラーが出なくなったら, その箇所がやはりあやしい。

変数の値を表示してみる

 エラーは出ないのだが結果はおかしい, というような場合は, 内部の処理が思うように行っていない。その場合は, いくつかの変数に着目して, その変数の内容を, 処理の途中で, printfなどで画面に表示してチェックしてみよう。

入力データや設定を単純化して、原因を切り分ける

 結果が望んだようにならない場合は、できる限り単純な入力・単純な設定にして試験するとよい。単純な入力・単純な設定なら、本来どういう結果になるべきかが容易にわかるため、実際の結果と正しい(本来あるべき)結果を比べることで不具合の発生原因を推測しやすい。

「良いソースコード」を書く

 ソースコードは, レポートや作文と同様に, 「知的作品」であり, 書き方に巧拙が存在する。うまく書かれたソースコードは思考の流れがわかりやすく可視化されるために, バグ自体が起きにくいし, デバッグもやりやすい。

 たとえ自分ひとりが使うプログラムでも, 客観的に他人の目でみたときにわかりやすくなっているかどうかを意識して書くことが, 良いソースコードへの第一歩である。それに, 君自身がどうしてもデバグできないときに, 友人や先生に助けを求める場合, 君のソースコードが汚ければ, あまり助けてもらえないかもしれない。

 良いソースコードを書くためのポイントは, 以下のとおりである:

  • シンプルに書く。
  • わかりやすいコメントをつける。
  • 記法に一貫性のあるルールを確立し, それを守る。
  • バージョン管理する。
  • デフォルトに頼らず, 明示的な定義を心がける。
  • マジックナンバーを埋め込むな(C言語入門5. 配列変数で学んだ)。
  • 他人の良いソースコードから学ぶ。

 良いソースコードを書く技術は, 良い論文や良いレポートを書く技術や, 良いプレゼンテーションをする技術と通ずるところが多い。自分が表現したいことを深く考えてシンプルに整理し上手に言語化するという頭の働きが大事である。

学生さんがよくやらかす, バク以前のミス

 以下のような単純ミスはバグではない。なので、いくらソースコードを眺めて悩んでも解決できないため、かえって学生さんを苦しめることが多い。

  • ソースコードを変更修正後, 保存しないでコンパイルしてしまう(変更修正が反映されないので、いつまでたってもうまくいかない)。
  • コンパイル後の実行ファイルとは違う実行ファイルを実行してしまう。
  • 誤った結果のファイルに追記モードで出力するため、いつまでもファイルの先頭に誤った結果が残ってしまう。
  • コンパイル時に必要なオプション(特にライブラリへのリンク指示)を忘れる。例えばmath.hを使っているのに, gccで-lmオプションを付け忘れる、とか。

課題9-1 以下のソースコード(kadai9-1.c)はコンパイルするとエラーが発生する。エラーメッセージをよく読み, デバッグせよ。

# include <stdio.h>
int main();
{
printf("Hello!\n");
}

課題9-2 以下のソースコード(kadai9-2.c)はコンパイルするとエラーが発生する。エラーメッセージをよく読み, デバッグせよ。

#include <stdio.h>
int main()
{
float i;
i=2.5;
printf("%d\n", i);
}

<C言語入門に戻る>

Last modified:2022/08/09 23:06:13
Keyword(s):
References:[C言語入門]