エラーとデバッグ

バグとは何か

プログラムの誤りのことをバグ(bug)という(由来).また,プログラムの誤りを正すことをデバッグ(debug)という.バグがあると,

  • コンパイル時,もしくは実行時にエラーがでて途中で終了する
  • プログラムがおかしな結果を返す

ため,バグを修正しなければならない

バグが見つかった場合、以下のことを思い出すようせよ(東京大学渡辺先生のページ; http://apollon.issp.u-tokyo.ac.jp/~watanabe/tips/debug.html).

プログラムにわけのわからないバグが発生した場合、(中略)最後までプログラムを見終わって「うーん、合っているはずだよなぁ」と つぶやくのがせいぜいである。 合っていたらバグなんて発生しない。そもそもバグが起きたら 「なんでだよ~」と思う時点で罠にはまっていると言ってよい。 バグは全て自分のせいだということを思い返そう。

エラーの種類

エラーには,コンパイルエラー(Compile Error),ランタイムエラー(Runtime Error),論理エラーの3種類がある。

コンパイルエラー

コンパイル(翻訳)時に起こるエラーである.3種類のエラーの中で一番修正の容易なエラーであり、原因のほとんどは文法ミスである.エラーの箇所とその内容が画面に表示される(エラーメッセージという)ので,その内容をみてソースファイルを修正する.

よくあるコンパイルエラーの原因には次のようなものがある.

  • スペルミス
  • 全角の空白文字を使っている
  • 引用符が閉じていない(「'」に対応する「'」がない.
  • Fortran77を使っていて,1行の文字数が72文字を超えている.

文法エラーの場合,エラーメッセージが示すバグの箇所より前に実際のバグが存在する場合があるので,注意せよ。

ランタイムエラー

実行時に生じるエラーである.ランタイムエラーが起こったら、次のコンパイルオプションをつけてプログラムを再コンパイルして、プログラムを再実行せよ。ランタイムエラーが起こった場所が特定できる場合が多い。

Intel Fortranの場合:

-traceback -CB -fpe0

gfortranの場合:

-fbounds_check -fbacktrace -ffpe-trap=invalid,zero,overflow

これらのオプションの意味については、コンパイラのオプション (Compiler Options)を参照せよ。

これらのエラーが発生したときには、Segmentation faultというエラーメッセージがよく表示される。簡単に言うと、Segmentation faultとは、データがメモリにうまく納まらない場合に表示される。Segmentation faultの詳細に関しては、ここを参照のこと。

よくあるランタイムエラーの原因を以下に列挙する。

配列のサイズオーバー

例えば,

dimension A(10)

として配列Aのサイズを10としているのに,

A(11)を参照したような場合.

良くやるミス

      dimension A(10)
      n=11
      do i=1,n
        A(i)=0.0
      enddo

このプログラムは,i=11のときエラーになる.

allocatable宣言をした配列の大きさを指定し忘れた

real,allocatable :: A(:)

と宣言しておいて、

allocate文を忘れると、Intel Fortran (ifort)の場合、segmentation faultというエラーメッセージが表示される。

この場合、

allocate(A(10))

などとして、配列Aの大きさを指定してやる必要がある。

存在しないファイルからデータを読もうとした

ファイル名が間違っていないかチェックすること

入力用ファイルのデータが足りない

データをどこまで読むことができているのかチェックすること

サブルーチンの引数の数があっていない

ifortの場合,実行時にSegmentation Faultというエラーが表示されることが多い.サブルーチン内部におかしいところが見当たらないときは,この種のバグがないかチェックせよ.

主プログラム

program main

real A

A=1.0

call sub(A)

.....

end program main

サブルーチン

subroutine sub(A, B)

real :: A, B

A=A+B

end subroutine sub

この例では、主プログラムとサブルーチンで引数の数が合っていないのでエラーになる。

サブルーチンの引数の型があっていない

主プログラム

program main

real A, B

A=1.0

call sub(A, B)

.....

end program main

サブルーチン

subroutine sub(A, B)

real(8) :: A, B

A=A+B

end subroutine sub

この例では、A, Bの型が合っていないのでエラーになる。これもSegmentation Faultというエラーが表示されることが多い。

バイトスワップオプションの指定ミス

バイナリデータの入出力をするときによくやるエラーです。

Intel fortran (ifort)では、コンパイル時に

-convert big_endian

というオプションを指定すると、バイナリ入出力時のデータの型がビッグ・エンディアンになります。データを作成したプログラムで、 -convert big_endianを指定しているのに、作成したデータを読むプログラムでは、 -convert big_endianを指定していないと、下記のようなエラーが出る。

forrtl: severe (39): error during read, unit ファイル番号, file 入力ファイル名

論理エラー

思ったとおりにプログラムが動作せず,おかしな値を返す場合である.これはプログラムに記載されている手順の誤り、ということなので、修正は人間がやる以外にない。修正するための処方箋としては,次のようなものがある.

  • 関係する変数の値をprint文で出力させて,値が正しいかチェックする.
  • if文の分岐が正しく行われているか,print文をつかって確認する.
  • 擬似データを作って,正しく動作するか確認する.
  • 動作がおかしい箇所のプログラムを出来る限り単純化したテスト用のプログラムを作って確認する.

重要なことは,上手く動くはずだ,という思い込みを捨てて,正常に動作するかどうか一つ一つ確認することである.

東京大学渡辺先生のページ (http://apollon.issp.u-tokyo.ac.jp/~watanabe/tips/debug.html))の「特別編」は必見である。

要点をまとめると以下のようになる。

  • おかしな結果がどのような要因で生じているのか仮説を立てる
  • 仮説は具体的なものとする(どこが,どのようにおかしいかまで述べる)
  • 仮説をリストアップしていく
  • リストアップした仮説について,ここがおかしければ,こうなるはず,というストーリーを考える
  • ストーリー通りの過ちを犯していないかどうか,一つ一つチェックしていく

論理エラーのチェックのためにプログラムを変更する場合には,どんな些細な変更であっても必ずバックアップをとること.デバッグのための変更によって,新たなバグが入ることを防ぐことができる.ソースファイルの容量などたかがしれているので,ケチケチせず,バックアップを取るようにする.

参考:ここの、bksrc.shというシェルスクリプトを使うと、簡単にソースファイルのバックアップがとれる。

備考

デバッグ用のツール(ソフトウェア)がいくつかある.有名なものとしては,GDB, DDD, Eclipseがある.ウェブサイトを検索してみるか,Matloff and Salzman (2009)を参照せよ。

GDBの初歩

また,参考になる文献として,

東京大学渡辺先生による資料(http://apollon.cc.u-tokyo.ac.jp/~watanabe/tips/debug.html)と小俣 (2007)を挙げる特に,渡辺先生の「特別編」は論理エラーのデバッグに非常に役に立つ。

参考文献

渡辺宙志. デバッグの方法論, http://apollon.issp.u-tokyo.ac.jp/~watanabe/tips/debug.html

小俣光之 (2007): プログラミングでメシが食えるか. 秀和システム, 303pp.

Matloff, N. and P. Salzman. 相川愛三訳 (2009): 実践デバッグ技法. オライリージャパン, 270pp.