C++多态实现原理:虚表 动态绑定

gdb打印对象虚函数指针

示例代码:Derive继承Base

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
using namespace std;

class Base
{
public:
Base(int data) :ma(data) { cout << "Base()" << endl; }
~Base() { cout << "~Base()" << endl; }
virtual void show() { cout << "Base::show()" << endl; }
void show(int, int) { cout << "Base::show(int, int)" << endl; }
protected:
int ma;
};
class Derive : public Base
{
public:
Derive(int data) :Base(data) { cout << "Derive()" << endl; }
~Derive() { cout << "~Derive()" << endl; }
void show() { cout << "Derive::show()" << endl; }
private:
int mb;
};
int main()
{
Derive d(10);
Base *p = &d;
p->show(0, 0);
p->show();

cout << sizeof(Base) << endl;
cout << sizeof(Derive) << endl;
cout << "-------" << endl;
cout << typeid(*p).name() << endl;
return 0;
}

通过gdb调试,设置断点后运行到断点位置,打印对象内存信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
ubuntu@ubuntu-ThinkCentre-M800t-1N000:~$ g++ virtual.cpp -o a.out -g
ubuntu@ubuntu-ThinkCentre-M800t-1N000:~$ gdb a.out
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.2) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...
(gdb) l 0
1 #include <iostream>
2 using namespace std;
3
4 class Base
5 {
6 public:
7 Base(int data) :ma(data) { cout << "Base()" << endl; }
8 ~Base() { cout << "~Base()" << endl; }
9 virtual void show() { cout << "Base::show()" << endl; }
10 void show(int, int) { cout << "Base::show(int, int)" << endl; }
(gdb)
11 protected:
12 int ma;
13 };
14 class Derive : public Base
15 {
16 public:
17 Derive(int data) :Base(data) { cout << "Derive()" << endl; }
18 ~Derive() { cout << "~Derive()" << endl; }
19 void show() { cout << "Derive::show()" << endl; }
20 private:
(gdb)
21 int mb;
22 };
23 int main()
24 {
25 Base b(5);
26 Derive d(10);
27 Base *p = &d;
28 p->show(0, 0);
29 p->show();
30 cout << "-----------------" << endl;
(gdb)
31 cout << sizeof(Base) << endl;
32 cout << sizeof(Derive) << endl;
33 return 0;
34 }
(gdb) b 33
Breakpoint 1 at 0x12ec: file virtual.cpp, line 33.
(gdb) r
Starting program: /home/ubuntu/a.out
Base()
Base()
Derive()
Base::show(int, int)
Derive::show()
-----------------
16
16

Breakpoint 1, main () at virtual.cpp:33
33 return 0;
(gdb) print b
$1 = {_vptr.Base = 0x555555557d38 <vtable for Base+16>, ma = 5}
(gdb) print d
$2 = {<Base> = {_vptr.Base = 0x555555557d20 <vtable for Derive+16>, ma = 10}, mb = 21845}
(gdb) print p
$3 = (Base *) 0x7fffffffdf70
(gdb) display *p
1: *p = {_vptr.Base = 0x555555557d20 <vtable for Derive+16>, ma = 10}
(gdb)

通过gdb打印可以看出,将派生类对象地址赋给基类指针,打印基类指针指向的对象内存,可以看到基类指针指向的对象内存中保存的vfptr虚函数表指针,该vfptr虚函数表指针会指向该对象类型的虚函数表,即指向派生类的虚函数表。

静态绑定和动态绑定

这里的绑定,指的是函数调用。静态绑定编译时期函数的调用就是确定的动态绑定函数的调用要到运行时期才能确定,动态绑定是实现OOP语言多态调用的技术基础。

类中出现虚函数,编译阶段会给该类型产生虚函数表,里面存放了虚函数的地址和RTTI指针

有虚函数的类实例化的对象,内存都多了一个vfptr虚函数表指针,指向该对象类型的虚函数表,同类型对象都有自己的vfptr,但是它们共享一个vftable。

基类指针调用派生类的同名覆盖方法时,发生了动态绑定,访问了基类指针指向对象的虚函数表vftable