このページの内容 |
---|
デバッガは万能ではない |
ブレークポイントを設定してみよう |
デバッガを起動してみよう |
思い通りにステップ実行 |
混合モードについて |
ありがたいスポンサー様 |
---|
『デバッガ』 と聞くと、デバッグを自動化してくれるツールのように思えてしまうかもしれません。デバッガは 『ここと、ここに、こんなバグがあるよー』 などと教えてくれそうです。デバッガを起動してカップラーメンを作ると、食べれる頃には全てのバグが修正されていそうです。
ところが実際は違います。デバッグするのはあくまで人間です。デバッガはデバッグ効率を上げるためのツールです。1行ずつアプリケーションを実行しながら、各変数の値を調べたりすることはできますが、異常があるかどうかを調べるのは人間であるところに注意して下さい。
デバッグ中では、プログラムを一時中断して、そのときの状態を調査したい場合があります。このような場合、ブレークポイントを設定するのが手っ取り早い方法です。
コンソールのデバッグ方法のページで使用したソースにブレークポイントを設定してみましょう。
#include <stdio.h> int calc_kaijo( int i) { if( i == 1) { return 1; } else { return i * calc_kaijo(i-1) ; } } void main() { // 5の階乗を計算する printf( "result : %d\n", calc_kaijo( 5)) ; }
calc_kaijo関数の中のif( i == 1)の部分にブレークポイントを設定してみます。if( i == 1)の行にカーソルを持っていき、 F9 キーを押すか、ビルド(ミニ)バーの手のボタンを押して下さい。ビルド(ミニ)バーは、メニューの上で右クリックしてビルド(ミニ)バーを選択して下さい。下のように、左にブレークポイントの印の赤丸が表示されます。
ブレークポイントの設定
ブレークポイントを削除するには、ブレークポイントのある行で、もう一度 F9 を押すか手のボタンを押して下さい。
デバッグする前に、まずはビルドする必要があります。デバッグする場合、通常はデバッグ版でビルドします。メニューの 『ビルド→アクティブな構成の設定』 で 『Win32 - Debug』 になっているか確認して下さい。なっていなければ、Win32 - Debug を選択して OK を押して下さい。
アクティブな構成をDebugに設定する
次に、メニューの 『ビルド->ビルド』 を選択するか、F7キーを押してビルドして下さい。ビルドがうまくいったら、準備完了です。デバッガを起動してみましょう。ビルド(ミニ)バーの手ボタンの左にある実行ボタンを押すか、F5 キーを押して下さい。メニューの 『ビルド→デバッグの開始→実行』 でもOKです。次のような画面で止まると思います。
デバッガの画面
この画面が、デバッガの画面です。左下が 『変数ウインドウ』、右下が 『ウォッチウインドウ』 です。変数ウインドウには、iの値が5と表示されています。デバッガ画面は通常の開発画面とは異なるので驚くかもしれませんが、アウトプットウインドウやワークスペースウインドウを表示すれば開発画面と同じにもできます。デバッガ画面は開発画面では表示されないいくつかの画面を表示できます(変数ウインドウはその一つです)
では、『コールスタックウインドウ』 を表示してみましょう。コールスタックウインドウには、関数呼び出しのスタックが表示されます。メニューの上で右クリックして コールスタックをチェックするか、メニューの 『表示→デバッグウインドウ→コールスタック』 を選択して下さい。コールスタックウインドウには、次のように表示されます。
calc_kaijo(int 5) line 11 + 12 bytes
main() line 18 + 7 bytes
mainCRTStartup() line 206 + 25 bytes
KERNEL32! 77e67d08()
コールスタックウインドウでは、下にあるものが上にあるものを呼び出していることを表します。つまり、main関数がcalc_kaijo(5)を呼び出し、mainCRTStartupがmain関数を呼び出しています。
それでは、実行ボタン(ショートカットはF5キー)を押しながら、変数ウインドウやコールスタックウインドウが変化していく様子を観察してみましょう。実行ボタンは、次のブレークポイントまで実行してくれます。次のようになります。
変数ウインドウとコールスタックウインドウの変化
calc_kaijo関数から、引数の値を1減らしてcalc_kaijo関数が呼ばれている様子が観察できると思います。そして、iの値が1のときに、F5 を押すと、次にはブレークポイントがないのでデバッグは終了します。
ステップ実行について解説する前に 『実行』 や 『リスタート』 の意味を整理しておきましょう。
それでは、ステップ実行について解説します。ステップ実行には『ステップイン(F11)』 『ステップオーバー(F10)』 『ステップアウト(Shift + F11)』の3種類があります。言葉だけを見ても、何のことだかちょっと分かりにくいですよね…。
ステップインとステップオーバーはプログラム内の命令を1つずつ実行していきます。両者は命令の中に関数呼び出しが含まれているときに動作が異なります。ステップインは関数が呼び出されているとき、その関数の内部の命令を1つずつ実行します。ステップオーバーは、関数呼び出しを実行したあとに、呼び出し部分の次の命令直前で停止します。
ステップアウトは、現在呼び出されている関数をreturnが呼ばれるまで実行して、関数の呼び出し側に戻って停止します。
分かりやすくするために次のようなコードを想定してみましょう。
#include <stdio.h> void b(){ printf( "b start\n"); printf( "b end\n"); } void a(){ printf( "start a\n"); b(); printf( "end a\n"); } void main() { printf( "start main\n"); a(); printf( "end main\n"); }
このプログラムの流れを書いてみると次図のようになります。下に行くほど時間が経過しており、main関数でa関数を、a関数でb関数を呼んでいる状態を図示しています。
サンプルの流れ
void a()の中のb();の行にブレークポイントを設定して、デバッグを開始したとします。この場合、次のようにb();の部分でプログラムは止まります。
ブレークポイントで止まった状態
黄色の矢印は次に実行される行を表します。つまり、b()はまだ実行されていないけれども、実行を再開した直後に実行される行です。
上のようにb();で停止したときに、ステップイン、ステップオーバー、ステップアウトを実行したときの停止位置を図示すると、次のようになります。(停止位置をわかりやすくするために、矢印を色分けしています)
それぞれのステップ実行1回後の停止場所
ステップイン(黄色)の場合は、b()関数の内部に入り、1行目を実行する前の場所で停止しています。ステップオーバー(赤色)の場合は、b()関数を実行してa()関数に戻ってきたところで停止しています。赤色矢印がprintf("a end\n");をさしているので、実行したときに最初にこの行が実行されます。ステップアウト(青色)の場合は、a()関数が終了するまで実行して、呼び出し側のmain関数に戻って停止しています。
ただし、呼び出す関数の中にブレークポイントがあった場合、ステップイン、ステップオーバー、ステップアウトともに、ブレークポイントで停止します。
それぞれのステップ実行1回後の停止場所
では、それぞれをどのように使い分ければよいでしょう。
基本的にはステップインだけで事足りるでしょう。しかし、呼び出す関数の中に明らかにバグがない場合や、printfなどのライブラリ関数を呼び出している箇所ではステップオーバーを使うと良いでしょう。また、現在ステップ実行中の関数の中には明らかに問題がない場合は、ステップアウトを実行して呼び出しがわに戻ると、ステップインを連打して関数を抜ける手間が省けます。
printf関数などのランタイムライブラリに含まれる関数にステップインしようとすると、printfのソースの場所を尋ねてきたあと、次のような画面になってしまいます。
逆アセンブルでステップ実行
上の画面は 『混合モード』 といい、逆アセンブルしたアセンブリ言語やバイトコード命令単位でブレークポイントを設定することができ、最適化されたコードのデバッグなどに便利です。今回はprintf関数のバイナリを逆アセンブルしたものになってしまいました。これは、printf関数のソースコードが見つからなかったため、混合モードで開かざるをえなくなったからです。このようになった場合は、ステップアウトした後、このウインドウを閉じてから呼び出し元のソースを見てください。黄色の矢印がちゃんと表示されているはずです。
また、main関数やWinMain関数からreturnで戻った場合にも、このような状態になってしまいます。これは、main関数やWinMain関数を呼んでいるmainCRTStartupやWinMainCRTStartup関数がランタイムライブラリに記述されているためです。
このような状態を避けるには、ランタイムライブラリの関数にはステップインせずにステップオーバーするように心がけるとよいでしょう。とはいえ、ステップインしてしまって混乱することもあるでしょう。そのために、ランタイムライブラリのソースコードをインストールするのはどうでしょうか。こうすれば、混合モードにはならずにソースコードにステップインできます。ランタイムライブラリのソースは、次のような方法でインストールできます。