1. 多继承代码
cpp
#include <iostream>
#include <cstdio>
using namespace std;
class Base1 {
public:
virtual void fooA() { cout << "Base1::fooA" << endl; }
virtual void fooB() { cout << "Base1::fooB" << endl; }
int baseValue1;
};
class Base2 {
public:
virtual void fooC() { cout << "Base2::fooC" << endl; }
virtual void fooD() { cout << "Base2::fooD" << endl; }
int baseValue2;
};
class Derived : public Base1, public Base2 {
public:
void fooA() override { cout << "Derived::fooA" << endl; }
void fooC() override { cout << "Derived::fooC" << endl; }
virtual void fooE() { cout << "Derived::fooE" << endl; }
int derivedValue;
};
int main() {
std::cout << "sizeof(Base1): " << sizeof(Base1) << std::endl;
std::cout << "sizeof(Base2): " << sizeof(Base2) << std::endl;
std::cout << "sizeof(Derived): " << sizeof(Derived) << std::endl;
Derived d;
d.fooA(); // Output: Derived::fooA
d.fooB(); // Output: Base1::fooB
d.fooC(); // Output: Derived::fooC
d.fooD(); // Output: Base2::fooD
d.fooE(); // Output: Derived::fooE
return 0;
}
运行结果:
bash
$ ./layout_part4
sizeof(Base1): 16
sizeof(Base2): 16
sizeof(Derived): 32
Derived::fooA
Base1::fooB
Derived::fooC
Base2::fooD
Derived::fooE
2. GDB打印内存布局
bash
(gdb) p d
$1 = {<Base1> = {_vptr.Base1 = 0x400d68 <vtable for Derived+16>, baseValue1 = 4196464}, <Base2> = {
_vptr.Base2 = 0x400d98 <vtable for Derived+64>, baseValue2 = 0}, derivedValue = 0}
(gdb)
继续查看虚表内容:
bash
(gdb) x/32gx 0x400d58
0x400d58 <_ZTV7Derived>: 0x0000000000000000 0x0000000000400de8
0x400d68 <_ZTV7Derived+16>: 0x0000000000400b30 0x0000000000400aac
0x400d78 <_ZTV7Derived+32>: 0x0000000000400b5c 0x0000000000400b8e
0x400d88 <_ZTV7Derived+48>: 0xfffffffffffffff0 0x0000000000400de8
0x400d98 <_ZTV7Derived+64>: 0x0000000000400b87 0x0000000000400b04
0x400da8 <_ZTV5Base2>: 0x0000000000000000 0x0000000000400e30
0x400db8 <_ZTV5Base2+16>: 0x0000000000400ad8 0x0000000000400b04
0x400dc8 <_ZTV5Base1>: 0x0000000000000000 0x0000000000400e48
0x400dd8 <_ZTV5Base1+16>: 0x0000000000400a80 0x0000000000400aac
0x400de8 <_ZTI7Derived>: 0x0000000000601d98 0x0000000000400e20
0x400df8 <_ZTI7Derived+16>: 0x0000000200000000 0x0000000000400e48
0x400e08 <_ZTI7Derived+32>: 0x0000000000000002 0x0000000000400e30
0x400e18 <_ZTI7Derived+48>: 0x0000000000001002 0x6465766972654437
0x400e28 <_ZTS7Derived+8>: 0x0000000000000000 0x0000000000601d40
0x400e38 <_ZTI5Base2+8>: 0x0000000000400e40 0x0000326573614235
0x400e48 <_ZTI5Base1>: 0x0000000000601d40 0x0000000000400e58
(gdb) info symbol 0x0000000000400de8
typeinfo for Derived in section .rodata of /home/test/layout_part4
(gdb) info symbol 0x0000000000400b30
Derived::fooA() in section .text of /home/test/layout_part4
(gdb) info symbol 0x0000000000400aac
Base1::fooB() in section .text of /home/test/layout_part4
(gdb) info symbol 0x0000000000400b5c
Derived::fooC() in section .text of /home/test/layout_part4
(gdb) info symbol 0x0000000000400b8e
Derived::fooE() in section .text of /home/test/layout_part4
(gdb) info symbol 0x0000000000400de8
typeinfo for Derived in section .rodata of /home/test/layout_part4
(gdb) info symbol 0x0000000000400b87
non-virtual thunk to Derived::fooC() in section .text of /home/test/layout_part4
(gdb) info symbol 0x0000000000400b04
Base2::fooD() in section .text of /home/test/layout_part4
可见,Base2中被重写的Derived::fooC() 函数指针被放到了_vptr.Base1虚表之中,没有被重写的Base2::fooD() 依然存放在_vptr.Base2
3. 图解内存布局
其中,指向红色函数代码段的函数指针位于Base1部分的虚表,指向绿色函数代码段的函数指针位于Base2部分的虚表。
可以看到,Base2中被重写的Derived::fooC() 函数指针被放到了_vptr.Base1虚表之中,Derived中新加的函数Derived::fooE()也放到了_vptr.Base1虚表之中。
4. thunk
thunk部分的gdb dump:
bash
(gdb) disass 0x0000000000400b87
Dump of assembler code for function _ZThn16_N7Derived4fooCEv:
0x0000000000400b87 <+0>: sub $0x10,%rdi
0x0000000000400b8b <+4>: jmp 0x400b5c <Derived::fooC()>
可见,是将指向Base2部分的指针向上偏移16,指向Derived对象的内存起始地址,然后跳转到Base1部分虚表里的Derived::fooC()
比如,通过基类指针来操作fooc:
Base2 *b2ptr = new Derived();
b2ptr->fooC();
将会对当前的this指针(b2ptr)向上偏移到Derived对象起始地址。