使用gdb探究C++內存佈局
行打印一個結構體成員 可以執行set print pretty on命令,這樣每行只會顯示結構體的一名成員,而且還會根據成員的定義層次進行縮進
按照派生類打印對象
set print object on
set p obj <on/off>
:
在C++中,如果一個對象指針指向其派生類,如果打開這個選項,GDB會自動按照虛方法調用的規則顯示輸出,如果關閉這個選項的話,GDB就不管虛函數表了。這個選項默認是off。 使用show print object查看對象選項的設置。
查看虛函數表
在 GDB 中還可以直接查看虛函數表,通過如下設置:set print vtbl on
之後執行如下命令查看虛函數表:info vtbl 對象或者info vtbl 指針或引用所指向或綁定的對象
- c++filt
GNU提供的從name mangling後的名字來找原函數的方法,如c++filt _ZTV1A,在命令行下運行
- 打印內存的值
gdb中使用“x”命令來打印內存的值,格式為“x/nfu addr”
。含義為以f格式打印從addr開始的n個長度單元為u的內存值。參數具體含義如下:
a)n:輸出單元的個數。
b)f:是輸出格式。比如x是以16進制形式輸出,o是以8進制形式輸出,a 表示將值當成一個地址打印,i 表示將值當作一條指令打印,等等。
c)u:標明一個單元的長度。b是一個byte,h是兩個byte(halfword),w是四個byte(word),g是八個byte(giant word)。
打印表達式的值
p 命令可以用來打印一個表達式的值。
使用如下:p/f 表達式
f 代表格式控制符,同上。
單繼承
#include <iostream>
class A
{
public:
int a;
A(): a(0x1) {}
virtual void foo()
{
std::cout << "A::foo()" << std::endl;
}
void bar()
{
std::cout << "A::bar()" << std::endl;
}
};
class B: public A
{
public:
int b;
B(): A(), b(0x2) {}
void foo()
{
std::cout << "B::foo()" << std::endl;
}
};
class C: public B
{
public:
int c;
C(): B(), c(0x3) {}
void foo()
{
std::cout << "C::foo()" << std::endl;
}
};
int main()
{
A a;
B b;
C c;
B* p = &c;
p->foo();
std::cout << sizeof(int) << " " << sizeof(int*) << std::endl;
return 0;
}
運行結果
C::foo()
4 8
int和int* 都是4個字節是在32bit 平臺 64位元是8個字節
,且p為基類B的指針,指向派生類C,virtual foo()函數運行時多態
對象內存佈局
(gdb) set p pre on
(gdb) p a
$1 = (A) {
_vptr.A = 0x405188 <vtable for A+8>,
a = 1
}
(gdb) p/a &a
$2 = 0x28ff24
(gdb) p/a &a.a
$3 = 0x28ff28
(gdb) p sizeof(a)
$4 = 8
(gdb) x/2xw &a
0x28ff24: 0x00405188 0x00000001
(gdb) set p vtbl on
(gdb) info vtbl a
vtable for 'A' @ 0x405188 (subobject @ 0x28ff24):
[0]: 0x403bf8 <A::foo()>
_vptr.A 代表a對象所含有的虛函數表指針,0x405188為第一個虛函數也即foo()的地址,真正虛函數表的起始地址為0x405188 - 8,還會有一些虛函數表頭信息
vptr 總是指向 虛函數表的第一個函數入口 對象a所在的地址為0x28ff24,整個對象佔8個字節,其中4個字節為vptr虛函數表指針,4個字節為數據int a
虛函數表 vtable for 'A' @0x405188
(gdb) p b
$5 = (B) {
<A> = {
_vptr.A = 0x405194 <vtable for B+8>,
a = 1
},
members of B:
b = 2
}
(gdb) p sizeof(b)
$6 = 12
(gdb) p c
$7 = (C) {
<B> = {
<A> = {
_vptr.A = 0x4051a0 <vtable for C+8>,
a = 1
},
members of B:
b = 2
},
members of C:
c = 3
}
(gdb) p sizeof(c)
$8 = 16
- 如果class B中申明瞭新的虛函數(比如foo2),class B中依然只有一個虛函數表,只不過會把foo2加入到該表中。此時class A的虛函數表不會包含foo2。
多重繼承
class A{
int a;
virtual void foo(){ std::cout << "A::foo()" << std::endl; }
};
class B{
int b;
virtual void bar(){ std::cout << "B::bar()" << std::endl; }
};
class C: public A, public B{
int c;
void foo(){ std::cout << "C::foo()" << std::endl; }
void bar(){ std::cout << "C::bar()" << std::endl; }
};
對象內存佈局
(gdb) set p pre on
(gdb) p a
$1 = (A) {
_vptr.A = 0x4051a0 <vtable for A+8>,
a = 4201067
}
(gdb) p b
$2 = (B) {
_vptr.B = 0x4051ac <vtable for B+8>,
b = 4200976
}
(gdb) p/a c
$3 = (C) {
<A> = {
_vptr.A = 0x4051b8 <vtable for C+8>,
a = 0x75e78cd5 <msvcrt!atan2+431>
},
<B> = {
_vptr.B = 0x4051c8 <vtable for C+24>,
b = 0xfffffffe
},
members of C:
c = 0x75e61162 <onexit+53>
}
(gdb) p sizeof(c)
$4 = 20
(gdb) x/5aw &c
0x28ff0c: 0x4051b8 <_ZTV1C+8> 0x75e78cd5 <msvcrt!atan2+431> 0x4051c8 <_ZTV1C+24> 0xfffffffe
0x28ff1c: 0x75e61162 <onexit+53>
- 數據成員int a, int b, int c都未初始化,此時是UB未定義行為
- 對象c含有兩個虛函數表指針_vptr.A和_vptr.B,佔用20個字節內存,3個int數據成員,兩個虛函數表指針
- 對象c的內存佈局為 c: vptr.A | a | vptr.B | b | c
虛繼承
#include <iostream>
using namespace std;
class A
{
virtual void foo()
{
cout << "A::foo()" << endl;
}
};
class B: virtual public A
{
virtual void foo()
{
cout << "B::foo()" << endl;
}
};
class C: virtual public A
{
virtual void foo()
{
cout << "C::foo()" << endl;
}
};
class D: public B, public C
{
virtual void foo()
{
cout << "D::foo()" << endl;
}
};
int main()
{
D d;
return 0;
}
對象內存佈局
(gdb) set p pre on
(gdb) p a
$1 = (A) {
_vptr.A = 0x405238 <vtable for A+8>,
a = 4201067
}
(gdb) p b
$2 = (B) {
<A> = {
_vptr.A = 0x405258 <vtable for B+28>,
a = 4200976
},
members of B:
_vptr.B = 0x405248 <vtable for B+12>,
b = 1978012002
}
(gdb) p c
$3 = (C) {
<A> = {
_vptr.A = 0x405278 <vtable for C+28>,
a = -2079145649
},
members of C:
_vptr.C = 0x405268 <vtable for C+12>,
c = 2686916
}
(gdb) p d
$4 = (D) {
<B> = {
<A> = {
_vptr.A = 0x4052a8 <vtable for D+44>,
a = 2686704
},
members of B:
_vptr.B = 0x405288 <vtable for D+12>,
b = -237228229
},
<C> = {
members of C:
_vptr.C = 0x405298 <vtable for D+28>,
c = 0
},
members of D:
d = 0
}
(gdb) p &d
$5 = (D *) 0x28fee8
- 對象內存佈局
a: vptr.A | a
b: vptr.A | a | vptr.B | b
c: vptr.A | a | vptr.C | c
d: vptr.A | a | vptr.B | b | vptr.C | c | d
- A pa = &d;B pb = &d;C*p c= &d;,都指向d的起始地址&d = 0x28fee8 。假如d類裡實現的虛
虛函數表裡到底有些什麼
/* vtable.cpp */
class A
{
public:
int ia;
virtual void foo()
{
cout << "A::foo()" << endl;
}
virtual void bar()
{
cout << "A::bar()" << endl;
}
};
class B: public A
{
public:
int ib;
virtual void foo()
{
cout << "B::foo()" << endl;
}
};
我們可以利用g++ -fdump-class-hierarchy vtable.cpp
得到class A和class B的vtable(結果在文件vtable.cpp.002t.class裡),如下所示:
Vtable for A
A::_ZTV1A: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI1A)
8 (int (*)(...))A::foo
12 (int (*)(...))A::bar