c-lang:デバッグ
虫潰し。バグ(bug)を直すこと。
デバッグの基本戦略
早期発見、早期治療
現状を見極める
原因を突き止める
解決策を見いだす
バグのソフトウェア工学用語
誤り(error)
計算された結果と正当な結果と間に生起する差
フォールト(fault)
コンピュータプログラム内に見られる、不正な行(step)、プロセス、およびデータの定義
故障(failure)
フォールトによって生成される不正な結果
人為的間違い(mistake)
不正な結果をもたらす人の行為
ソフトウェア故障(Software failure)
ソフトウェアが期待通りに動作しない、正しく機能しない現象を言う。
ソフトウェアフォールト(Software fault)
ソフトウェア故障の原因。プログラム内の欠陥や誤りをさす。
ソフトウェアエラー(Software error)
原因となった作業中に犯した開発者の誤り。
要求仕様の段階や設計段階で間違いが入った場合は、要求仕様定義エラーとか、設計エラーとか呼ばれる。
デバッグに向いている人
鋭い直感を備え、全体を見渡せる人
忍耐力があり、しかも、細かいところにも注意を向ける人
よいデバッガを持っている人
デバッグに向いていない人
無責任な人
わがままで協調性のない人
デバッグの小技
休止をとること。
時間があれば1、2日置いて、睡眠をとり、頭をすっきりさせてからデバッグを再開します。 驚くほど簡単にバグが見つかるでしょう。
システム管理者があなたの質問に答えられるかもしれないし、ニュースグループに質問をだしてみるのも良いでしょう。
たとえ少しでもC を知っている知り合いがいたら、コードを見てもらいましょう。
違う人の目で見るとすぐ見つかるかもしれません。
最後の手段は、書き直すことです。
ある部分がどうしても問題を引き起こすなら、その部分を消去して書き直してみます。
もっとよい方法を思いついて、間違いが減ったり、わかりやすくなるかも知れません。
デバッグはプログラミングの一部であることを忘れないこと。
デバッグに時間を費やすことになってもあわてないよう、計画に含めておきましょう。
お金のかかった仕事なら、デバッグに要する時間もきちんと見積もること。
経験を積めば時間も見積もることができるようになりますが、熟練してもデバッグの時間は要します。
バグの原因
拙速(Haste)
無関心(Apathy)
狭量(Narrow mindedness)
不精(Sloth)
強欲(Avarice)
無知(Ignorance)
高慢(Pride)
バグの原因として考えられるもの
仕様の甘さ
開発途中での仕様変更
テストの難しさ
不適切なバグ修正による、新たなバグの作り込み
開発者のスキル
開発者のストレス
ユーザーの無知
コンパイラの出力
いつでもコンパイルくらいはできる状態にしておく
できるだけ早期に対応つける
ブロックは最初は中身を書かず、{}だけを書く
入力後は、まずはコンパイルしてみる
できるだけチェックを厳しくする。ANSIのCを使う
ワーニングは大切な情報
ワーニングはエラーのうち
コンパイラの出力メッセージの意味が良くわからない。
頑張って解読しろ。
データ破壊
データがあやしい
データはいつでも壊される
データが大切。プログラムは付属品
文字列処理は、オーバーランする
文字配列(文字列)は、大きめのサイズを確保する
strncpy strncat を使用するとC言語の文字列の形式にならないかもしれない
パラメータのとき、文字列の長さは調べられる、しかし、サイズは調べられない
スコープの外も破壊される
スコープは便利さを提供する。安全は提供しない
変数は宣言順に確保されるとは限らない
変数と変数の間には隙間があるかもしれない
unsigned と signed を混合した式は危険
関数中の変数は、関数の実行終了時に破棄される
ローカル変数の初期値は不定(ゴミ)である
ローカル変数はスタックを使いまわしする
動的記憶管理は、一度壊れだすと連鎖反応的に破壊が進む
動的記憶割りつけは素人には厳禁
free の忘れは、忘れたころに影響がでる
キャストは誤りさえ正当化する
データが破壊されたら、直前のデータを疑う
間違いやすいパラメータのある関数は作らない
構造体のメンバの並びに依存するプログラムは組むな
char の型変換にも注意しろ
オーバーフロー
atoi() を気安く呼ぶな
かけ算には注意しろ
加減算にも注意しろ
ネットワークの落とし穴
トラフィックの最大量 トラフィック量がピークに達して処理に支障が出たとき、さらにトラフィック量が増加する。
計算量が爆発してしまう
不適切なアルゴリズムを選択すると、どんな処理能力が優れたコンピュータを用意しようとも、計算量が莫大な量になるので、いつまでたっても計算が終了しなくなる。
ちょっとデータが増えるだけで、爆発的に計算量が増えてしまう場合がある。 データが少量な場合はすぐに答えは出るけれども、入力データの量が少々多くなっただけでコンピュータが計算を繰返し、有効時間内に答えが求められないということになる。
いつまでたっても終わらない
排他制御
アクセスが集中しないようなシステムを設計する。
malloc の呪い
(そのうち)
浮動小数点演算の落とし穴
浮動小数点演算は、本来正確なものではない。
デバッグしやすいコーディング
関数はシンプルに
引数は、必ずチェックする
戻り値も必ずチェックする
エラーが発生した場合は、エラーが発生した場所を特定できるようにしておく
巧妙なコードはなるべく書かない
デバッキングエイド
ダンプルーチン(dump routine)
主記憶装置や外部記憶装置に記憶されている内容を印刷装置などに出力するルーチン。
検査ルーチン(test routine)
プログラムの中に、適当なチェックポイントを設け、プログラムの実行がチェックポイントに来たとき、主記憶装置の内容やループの回数など必要な情報を出力する。
追跡ルーチン(tracer)
プログラムの実行状態を逐次チェックし、実行中のプログラムが正しく動作しているか調べるためのものである。 デバッグライト(debug write)、スナップショット(snapshot)
プログラム中の適当な場所にprintfなどで変数や、プログラムの経過の表示を行う。
プログラムを実行した際に表示される変数の値や経過の表示を使ってプログラムの流れを追っていく(trace)ことによりバグを見つける。
printfデバッグ
hello world が書ければ、使うことができる。
ソースコードの節目、節目に変数の値を表示するコードを入れて置く。
好きな書式で変数を出力することが出来る。
デバッグライトは、fputs()、puts()、putchar()などは使わず、printf()またはfprintf() で統一したほうがよい。
デバッグ用フック
その関数は、どこからも呼ばれないが、実行ファイルの中に格納される。 これが「デバッグ用フック」である。
コードのデバッグが始まり、ブレークポイントまでたどり着いたときも、表示ルーチンを起動するだけでデータ構造の正誤を簡単に確認できる。
異常データの検出
ある処理や計算を行う前後に、その処理で使用している変数や途中結果、最終結果を表示することによって処理が正しく行われているか分かる。
例えば割り算をしている部分の前に変数の値を表示することによって 0 で割っていないかをチェックできる。
プログラムの流れの異常
プログラムの主要な部分ごと(処理のまとまりごと)にその部分を実行する前にその処理内容を表示する。
プログラムが途中で停止したり、暴走した場合、表示した処理の中にバグがある事が分かる。
例えばループ処理の前と後ろにデバッグライトを記述しておき、前のデバッグライトが表示された直後に、後ろのデバッグライトを表示しないで、プログラムが暴走すれば、そのループ処理の中にバグがあることが確認できる。
条件付きコンパイル
code:cpp
/* デバッグライト */
/* デバッグライト */
デバッグマクロ
/*
* デバッグマクロ
*/
#define Msg(str) fprintf(stderr,"Dbg%s\n",(str)) RS232Cでデバッグライトを出力する
できるらしい。
TCP/IPでデバッグライトを出力する
できるらしい。
二分法
(そのうち)
ログファイル出力
デバッグにデバッガを使用できない、あるいは使用しにくいケースというのは結構あるものです。 たとえば、CGIなどではWEBサーバから勝手に起動され、環境変数などによって動作しますし、大抵はあっという間に処理を終えて終了してしまうのでデバッガをアタッチするのも困難です。
また、リアルタイム的に処理するようなプログラムも同様に、デバッガを使ってのんびりステップ実行などしていては再現できないケースもあります。
こんな場合は、初心に返り、標準エラー出力やログファイルにどんどん状態を出力しましょう。
デバッガの存在を知らなかった頃は大抵誰もがこうやってデバッグしていたと思います。
CGIの場合はWEBサーバによっては標準エラー出力に出力するとエラーになる場合もありますので、ログファイルを使いましょう。
デバッグ出力用の関数を準備しておくのが便利で、可変引数によってprintf()的なデバッグ出力を可能にしたり、ログファイルのサイズが一定以上になったら切り替えたり、削除したりする処理も入れておくと便利です。
また、専用に用意しておけば不要になった際に出力を停止するのも簡単です。
デバッガ(ツール)の使用
(別記予定)
机上デバッグ
プログラムリストをプリンタで打ち出してチェックする。
変数の型が正しいか、式の記述が間違っていないか、
必要なヘッダファイルのインクルードを忘れていないか、
ループの継続脱出条件にミスがないか、
if文中の条件判断が間違っていないか
などを中心に見る。
CPUの占有時間が貴重だった昔は主流だったらしいが、今は流行らない。
今は、対話的にデバッグする方法が主流である。
気づいたところをチェックしたり、書き込みを入れたりできる。
そして、それは消えたりしない。
他人が作った、ひどいソース(関数が馬鹿でかい、構造化されていない、意味不明なコードがいっぱい書いてある など)を追跡する場合には役に立つ。 デシジョンテーブル(decision table:決定表)
決定表は、情報を整理するために用いられる代表的な手法であり、システム開発 においても設計やテストの段階で補助資料として活用されるものである。 判断と、その結果行われる処理との関係を表の形で表わしたもの
条件と処理との関係が明瞭かつ、簡潔に表わせるので、複雑な論理を一目で解るように表現できる
デシジョンテーブルの構成
デシジョンテーブルは、条件項目欄、行動項目欄、条件記入欄、行動記入欄から構成される。
条件項目欄
条件項目欄(condition stab : 条件スタブ)は、その問題で検査すべき全ての条件を記入する欄である。
行動項目欄
行動項目欄(action stab : 行動スタブ)は、特定の条件が満足されたときに実行する全ての行動あるいは処理を記入する欄である。
条件記入欄
条件記入欄(conditon entry : 条件エントリ)は、条件項目欄の各項目に対して、Yesまたは、Noの二者択一を記入する欄である。 行動記入欄
行動記入欄(action entry : 行動エントリ)は、条件項目欄上の各条件の組み合わせに対して実行すべき行動項目を指定するための欄で、該当する欄にチェックマークを入れる。
table:d1
条件項目欄 条件記入欄
行動項目欄 行動記入欄
table:d2
1 2 3 4 5 6 7 8
条件項目 A を満足する N N N N Y Y Y Y
B を満足する N N Y Y N N Y Y
C を満足する N Y N Y N Y N Y
行動項目 処理 P を行なう x x
処理 Q を行なう x x x
処理 R を行なう x x
デシジョンテーブルの作成例 閏(うるう)年の判定
閏年の判定は
4の倍数でなければ閏年ではない。
4の倍数なら閏年である。
ただし、4の倍数でも100の倍数は閏年ではない。
しかし、100の倍数でも400の倍数なら閏年である。
これを、決定表で表すと以下のようになる。
table:d3
1 2 3 4
西暦年は4の倍数である N Y Y Y
西暦年は100の倍数である。 - N Y Y
西暦年は400の倍数である。 - - N Y
閏年である。 X X
閏年ではない。 X X
クリーンルーム手法(Clean Room 手法)
クリーンルーム手法は、最初から欠陥の無いソフトウェアを開発することを目指して、IBMにいた Harlan Mills 氏らが長年掛けて開発した「開発手法」で、1987年に発表されました。
それ以来、各国の研究者やソフトウェアの開発組織において実際に取り組まれてお り、1990年に入って、その成果が報告されるに及んで、一躍注目されるようになりました。
この手法の特徴は、ソフトウェアの開発者は一切テストを行わないことです。
もちろんそのために開発段階で緻密なレビューと、それを支えるべく厳密なトップダウンによって設計が進められています。
言い換えればレビュー(ここではインスペクションと呼んでいます)のみで、欠陥の侵入を防いでいるのです。
テストは別の評価部隊が行いますが、その結果、一定の件数以上の不具合が検出されれば、修正されることなく“廃棄処分”となります。
つまり、修正に値しない、というわけです。
この「一定以上の不良率」を検出すればそのロットを全量廃棄するというやりかたは、LSIの製造設備である「clean room 」にちなんだもので、名称の由来もそこにあります。
単体テスト
プログラムの分岐の全ルートを通るように、テストデータを用意する。
ループ処理では、0回、1回、最大回数の処理を行なう。
入力データとして許される範囲の境界上のテストデータを用意する。
特に、数値データでは、0の場合もテストする。
許容範囲の境界上の出力データが得られるようなテストデータを用意する。
明らかな誤りのデータを入力し、プログラムが誤動作しないことを確認する。
その他
見えない「空白」や「タブ」もバグになる
「漢字の空白」は要注意
暴走はたちがよい
まともな OS なら、暴走しても安心
バグは境界に住みつく
修正がいちばん危険
問題は小さくしてから解く
可変パラメータのとき、すべての責任は呼び出し側にある
マクロ展開されたソースも調べる
正しい結果を与える標準となるべきプログラムを必ず用意する。
コンパイラのオプションを変えて実行(例、最適化のレベルを下げる)。
異なるシステム(OS、機種など)同士で実行、比較してみる。
デバッグ作業の履歴をきちんと記録しておく
デバッグに伴うプログラムの変更箇所には必ず注釈をつける。
先入観(思い込み)を棄てる。(虚心坦懐に)
デバッグ作業を面倒がらない。(急がば回れ)
可能な限り妥協してはいけません。(結局問題を先送りするだけ。)
末期段階、バグも仕様の内と開きなおる(本当は絶対やっちゃいけない)