gdbでSTLのコンテナの値を表示する方法

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]

とやってしまう訳なのだが,結論からいうとこれは出来ないのだ.


STLvectorでn番目の要素にアクセスする場合,内部では vector::operator[](n) という関数が呼ばれている.つまり,

array[0]

というコードは

array.operator[](0)

というコードに展開され,コンパイルされている.そのため gdb で array[0] の値を確認したい場合も,operator[]() 関数が必要になる訳である.

gdbは任意の関数を起動してその結果を確認することもできる.たとえば

(gdb) p  array.size()

とタイプすれば,ベクタの要素数が参照できる.つまりデバッガ上でC++の関数std::vector::size()を実行して,その結果を表示している訳である.同じように

(gdb) p array.operator[](0)

を実行すればarray[0]の値が確認できそうなのだが,関数operator()の実行は失敗する.なぜなら operator() は常にインライン展開される関数なので,関数の実体が存在せず,デバッガから起動できないのである.


じゃあ,どうするか.前置きが長くなったが,ここからが本題である.問題としては,STLvectorのある要素を表示したい場合は

  • 関数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インスタンスを作っているのでポインタの型はすべてintである.

よって 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::size()を起動できるあたりがデバッガの醍醐味でもある.

*1:_M_impl._M_start + n のアドレス計算を int* で行っている.つまり n番目の要素は,先頭アドレス_M_impl._M_start から n * sizeof(int)バイト後ろの領域にある