初めてのgdb

実はデバッガなんてEmacs Lisp用に標準搭載されているものを起動したことがある、という程度で、ぜんぜん使ったことがないのだけど、jnishinoさんのすすめに従ってEmacsgdb-modeを起動してみた。まず先に、gcc -gで、デバッグのための情報を含むa.outを作って、おもむろに「M-x gdb」とかやると、新規バッファが開いてgdbが起動する。で、runとかやると、通常の実行では「Segmentation fault」としか出ていなかったエラーが、ちゃんと該当個所の関数名や行番号とともに表示されるようになった。

 :
 :
Program received signal SIGSEGV, Segmentation fault.
0x080485e0 in DeleteNode (list=0xbfa10bd4) at list.c:88
(gdb) 

gdbでは、ブレークポイントを設定したり、ステップ実行したり、実行中の変数を表示したりもできる。ポインタの中身のアドレスを見ても何の役にも立たないんじゃないかとか一瞬思ったりしたけど、どれとどれが同じアドレスを指しているかとか、実は気づいたらNULLポインタだったとか、そういうのが分かるので、かなり助けになる。

実際、gdbでステップ実行して分かった。薄々そうじゃないかと思っていたNULLポインタ関係のバグという当たりをつけて、DeleteNode関数を書き換えた……、と思ったら、書き換えたコードを誤って消してしまった。

Gitを使ってバグありコードのほうを再現しようとブランチを切ったのはいいけど、実はmasterのほうで書き換えたコードをcommitしていなかった。masterもbug再現ブランチも古いコードになっている状態というのに気づかずに、Emacsのバッファを消してしまった。せっかく書き換えたやつが消えちゃったってことか。寝る前に書いた数行のコードなんだから30秒で復元できてよさそうなものだけど、爽やかに目覚めた朝には、ちょっとしたダメージがある。まあ、もう1度、反復練習を強制的にやるいい機会と思うことにしよう。面倒に感じるのは、具体的で確固としたデータ構造と、その操作のイメージが頭にないからなんだろうと思う。データ構造という話をするとき、これまで抽象的な箱がつながることだと思っていた。でもCレベルでは、それは色々なポインタの操作であって、これってパワポでいえば、箱と箱を線で結ぶのを手作業でやるような話なんだなと理解した。「箱」を消すというのはつまり結線も手作業でやり直すってことで、しかも目に見えないもんだからこれは人間には難しいに決まってる。RubyとかJavaのコレクションのありがたさって、そういうことなんだろうな。