STLを使ったコード,たとえば
std::vector<int> array;
というコードがあったとして,gdbでデバッグ中に array[0] の値を確認したい場合はどうするか?実はこれが結構面倒なのだ.
gdbでデバッグ中に,変数の値を確認したい場合は print コマンドを使う.例えば
int a=12;
というコードがあって,変数 a の値を表示させたい場合は gdb のコマンドラインで
(gdb) print a
とする.printコマンドは p と省略して記述できるので,通常は" p a "で確認する.さらに,変数が配列の場合なら "p 変数名[添字]" となる.
以上をふまえると,
std::vector<int> array;
というコードがあったとして,先頭の array[0] の値を確認したい場合は
(gdb) p array[0]
とやってしまう訳なのだが,結論からいうとこれは出来ないのだ.
STLのvectorでn番目の要素にアクセスする場合,内部では vector
array[0]
というコードは
array.operator[](0)
というコードに展開され,コンパイルされている.そのため gdb で array[0] の値を確認したい場合も,operator[]() 関数が必要になる訳である.
gdbは任意の関数を起動してその結果を確認することもできる.たとえば
(gdb) p array.size()
とタイプすれば,ベクタの要素数が参照できる.つまりデバッガ上でC++の関数std::vector
(gdb) p array.operator[](0)
を実行すればarray[0]の値が確認できそうなのだが,関数operator()の実行は失敗する.なぜなら operator() は常にインライン展開される関数なので,関数の実体が存在せず,デバッガから起動できないのである.
じゃあ,どうするか.前置きが長くなったが,ここからが本題である.問題としては,STLのvectorのある要素を表示したい場合は
- 関数operator[]() をつかう必要がある
- でもバイナリ中に 関数operator[]()の実体がない
のふたつである.でもgdbと言えども無い関数は起動できない.そこで,呼べないならgdb側で作ってしまえというアプローチを取ってみる.
まず,operator[]() が内部で何をしているかを調べてみる.このような作業は,STL::vectorのソース(stl_vector.h)を読むよりもgdbを使った方が早い.オブジェクト指向的アプローチ(?)でまずデータ構造を確認する.
(gdb) p array $15 = { <std::_Vector_base<int,std::allocator<int> >> = { _M_impl = { <std::allocator<int>> = { <__gnu_cxx::new_allocator<int>> = {<No data fields>}, <No data fields>}, members of std::_Vector_base<int,std::allocator<int> >::_Vector_impl: _M_start = 0x804b028, _M_finish = 0x804b038, _M_end_of_storage = 0x804b038 } }, <No data fields>}
このように vector は内部で _M_start, _M_finish, _M_end_of_storage という3つのポインタを持っていることが判る.また今回は std::vector
よって operator[](n) のアルゴリズムとしては _M_impl._M_start[n] の値を参照しているだけであることがわかる*1.実際にgdbで確認してみる.
(gdb) p array._M_impl._M_start[0]
要素の値が表示されるので,どうやら正しいらしい.
しかし,毎回 _M_impl._M_start[0] なんてタイプするのはしんどい.そこで,gdbのマクロを書いてみた.
define print_vector print $arg0._M_impl._M_start[$arg1] end
このマクロ print_vector を使えば
(gdb) print_vector array 0
で要素の0番目の値が確認できる.
もうすこし実用性を高めるために,添字の範囲チェックを入れると次のようになった
define print_vector if ($arg1 <0 || $arg0.size() <= $arg1) print "out of range" else print $arg0._M_impl._M_start[$arg1] end end
$arg0.size() と書くだけで C++の関数stl::vector
*1:_M_impl._M_start + n のアドレス計算を int* で行っている.つまり n番目の要素は,先頭アドレス_M_impl._M_start から n * sizeof(int)バイト後ろの領域にある