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

相关推荐
拾木20010 分钟前
同步io和异步io
java·开发语言·python
shuai_25812 分钟前
深入解析C++单例模式:从基础到线程安全的高效实现
开发语言·c++·qt
黒井深27 分钟前
Visual Studio(vs)下载安装C/C++运行环境配置和基本使用注意事项
c语言·c++·ide·visual studio
东离与糖宝29 分钟前
Rust 所有权 简介
开发语言·后端·rust
你可以自己看32 分钟前
初学者如何掌握python
开发语言·python
秋风起,再归来~32 分钟前
C++从入门到起飞之——继承上篇 全方位剖析!
开发语言·c++·继承
大柏怎么被偷了1 小时前
【Qt】子控件选择器
开发语言·c++·qt
十五年专注C++开发1 小时前
CTK框架(十):PluginAdmin插件
开发语言·c++·qt·插件开发·ctk
干货没人看1 小时前
惩罚矩阵?动态规划是如何爱上矩阵的
c语言·c++·经验分享·算法
纵横君=_=1 小时前
Day8 | Java框架 | Maven
java·开发语言·maven