C++ 内存布局 - Part4: 多继承与this指针调整

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对象起始地址。

相关推荐
数据小小爬虫几秒前
如何利用Python爬虫获取商品历史价格信息
开发语言·爬虫·python
Gao_xu_sheng3 分钟前
Java程序打包成exe,无Java环境也能运行
java·开发语言
NiNg_1_2348 分钟前
Python的sklearn中的RandomForestRegressor使用详解
开发语言·python·sklearn
谢家小布柔11 分钟前
java中的继承
java·开发语言
黑色叉腰丶大魔王12 分钟前
《基于 Python 的网页爬虫详细教程》
开发语言·爬虫·python
l1384942745117 分钟前
Java每日一题(2)
java·开发语言·游戏
晓纪同学25 分钟前
QT创建一个模板槽和信号刷新UI
开发语言·qt·ui
WANGWUSAN6631 分钟前
Python高频写法总结!
java·linux·开发语言·数据库·经验分享·python·编程
forNoWhat40 分钟前
java小知识点:比较器
java·开发语言
坐井观老天1 小时前
在C#中使用资源保存图像和文本和其他数据并在运行时加载
开发语言·c#