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

相关推荐
普if加的帕5 分钟前
java Springboot使用扣子Coze实现实时音频对话智能客服
java·开发语言·人工智能·spring boot·实时音视频·智能客服
2301_8076114935 分钟前
77. 组合
c++·算法·leetcode·深度优先·回溯
安冬的码畜日常1 小时前
【AI 加持下的 Python 编程实战 2_10】DIY 拓展:从扫雷小游戏开发再探问题分解与 AI 代码调试能力(中)
开发语言·前端·人工智能·ai·扫雷游戏·ai辅助编程·辅助编程
朝阳5811 小时前
Rust项目GPG签名配置指南
开发语言·后端·rust
微网兔子1 小时前
伺服器用什么语言开发呢?做什么用什么?
服务器·c++·后端·游戏
朝阳5811 小时前
Rust实现高性能目录扫描工具ll的技术解析
开发语言·后端·rust
程高兴1 小时前
基于Matlab的车牌识别系统
开发语言·matlab
YuforiaCode1 小时前
第十三届蓝桥杯 2022 C/C++组 修剪灌木
c语言·c++·蓝桥杯
YOULANSHENGMENG2 小时前
linux 下python 调用c++的动态库的方法
c++·python
牛马baby2 小时前
Java高频面试之并发编程-07
java·开发语言·面试