結論
C++をgdbでデバッグする時(特にポリモーフィズム使ってる場合)は、下記の設定をするとわかりやすいです。
set print object on
背景
C++でポリモーフィズムすると、デバッグ時に実際のクラスがぱっと見わからなくて困ることがあります。
例として、以下のようなプログラムを考えます。
Animal
クラスを基底クラスとして、そこから継承したクラスをいくつか定義しています。
それらのインスタンスへのポインタをstd::vector
でまとめて、
Chorus
関数にてポリモーフィズムを使ったMakeSound
呼出をしています。
#include <iostream>
#include <memory>
#include <vector>
class Animal {
public:
virtual ~Animal() {}
virtual void MakeSound() const { std::cout << "#!@" << std::endl; }
};
class Cat : public Animal {
public:
virtual ~Cat() {}
virtual void MakeSound() const { std::cout << "Meow" << std::endl; }
};
class Dog : public Animal {
public:
virtual ~Dog() {}
virtual void MakeSound() const { std::cout << "Bow" << std::endl; }
};
class Human : public Animal {
public:
explicit Human(int age) : age_(age) {}
virtual ~Human() {}
virtual void MakeSound() const { std::cout << "Hello" << std::endl; }
private:
int age_;
};
class Baby : public Human {
public:
explicit Baby(int age, bool can_stand) : Human(age), can_stand_(can_stand) {}
virtual ~Baby() {}
virtual void MakeSound() const { std::cout << "Baboo" << std::endl; }
private:
bool can_stand_;
};
static void Chorus(const std::vector<std::unique_ptr<Animal>>& animals) {
for (const auto& animal : animals) {
animal->MakeSound();
}
}
int main() {
std::vector<std::unique_ptr<Animal>> animals;
animals.push_back(std::make_unique<Animal>());
animals.push_back(std::make_unique<Cat>());
animals.push_back(std::make_unique<Dog>());
animals.push_back(std::make_unique<Human>(20));
animals.push_back(std::make_unique<Baby>(3, true));
Chorus(animals);
return 0;
}
このChorus
関数をgdbでデバッグすると、デフォルトでは下記のような出力が得られます。
(gdb) b Chorus(std::vector<std::unique_ptr<Animal, std::default_delete<Animal> >, std::allocator<std::unique_ptr<Animal, std::default_delete<Animal> > > > const&)
Breakpoint 1 at 0x1076: file animal.cc, line 41.
(gdb) r
Starting program: /home/gdb_macro/sample/animal
Breakpoint 1, Chorus (animals=std::vector of length 5, capacity 8 = {...}) at animal.cc:41
41 static void Chorus(const std::vector<std::unique_ptr<Animal>>& animals) {
(gdb) p animals
$1 = std::vector of length 5, capacity 8 = {std::unique_ptr<Animal> = {
get() = 0x55555576ee70
}, std::unique_ptr<Animal> = {
get() = 0x55555576eeb0
}, std::unique_ptr<Animal> = {
get() = 0x55555576ee90
}, std::unique_ptr<Animal> = {
get() = 0x55555576eed0
}, std::unique_ptr<Animal> = {
get() = 0x55555576ef20
}}
(gdb) p *animals[4]
$2 = {
_vptr.Animal = 0x55555575bc08 <vtable for Baby+16>
}
animals
の出力からは、Animal
へのstd::unique_ptr
ということしかわかりません。
そこで、要素のポインタが指す先を確認したところ、
vtableのアドレスからBaby
クラスのインスタンスっぽいことはわかりますが、
Baby
クラスが持つメンバ変数などの情報は表示されません。
表示するためには、下記のように一旦Baby
クラスのポインタにキャストする必要があります。
(gdb) p *(Baby*)animals[4]
$3 = {
<Human> = {
<Animal> = {
_vptr.Animal = 0x55555575bc08 <vtable for Baby+16>
},
members of Human:
age_ = 3
},
members of Baby:
can_stand_ = true
}
解決策
このキャストのように、実際のクラスとして解釈し出力するようにするgdbの設定が、以下です。
set print object on
この設定を行うと、キャストなしでBaby
型として解釈してくれます。
(gdb) set print object on
(gdb) p *animals[4]
$11 = (Baby) {
<Human> = {
<Animal> = {
_vptr.Animal = 0x55555575bc08 <vtable for Baby+16>
},
members of Human:
age_ = 3
},
members of Baby:
can_stand_ = true
}
応用
さらに、以下のようなgdb macroを定義しておくと、
STLコンテナに保持したポインタの先を、実際のクラスとして解釈して出力することができます。
define print_each_pointee
if $argc == 0
help print_each_pointee
else
set $size = $arg0.size()
set $i = 0
while $i < $size
printf "$arg0[%u]: ", $i
p *$arg0[$i]
set $i++
end
end
end
document print_each_pointee
Prints each pointee of STL container member
Syntax: print_each_pointee <container>
end
set print pretty on
set print object on
(gdb) source print_each_pointee.gdb
(gdb) print_each_pointee animals
animals[0]: $12 = (Animal) {
_vptr.Animal = 0x55555575bca8 <vtable for Animal+16>
}
animals[1]: $13 = (Cat) {
<Animal> = {
_vptr.Animal = 0x55555575bc80 <vtable for Cat+16>
}, <No data fields>}
animals[2]: $14 = (Dog) {
<Animal> = {
_vptr.Animal = 0x55555575bc58 <vtable for Dog+16>
}, <No data fields>}
animals[3]: $15 = (Human) {
<Animal> = {
_vptr.Animal = 0x55555575bc30 <vtable for Human+16>
},
members of Human:
age_ = 20
}
animals[4]: $16 = (Baby) {
<Human> = {
<Animal> = {
_vptr.Animal = 0x55555575bc08 <vtable for Baby+16>
},
members of Human:
age_ = 3
},
members of Baby:
can_stand_ = true
}
環境
以下の環境で動作確認しています。
$ uname -r
5.5.8
$ lsb_release -d
Description: Ubuntu 18.04.5 LTS
$ gdb --version | head -1
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
$ gcc --version | head -1
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
gdb macro及びサンプルコードはこちらにあげています。