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

相关推荐
乌萨奇也要立志学C++3 分钟前
【C++详解】STL-list模拟实现(深度剖析list迭代器,类模板未实例化取嵌套类型问题)
c++·list
presenttttt13 分钟前
用Python和OpenCV从零搭建一个完整的双目视觉系统(四)
开发语言·python·opencv·计算机视觉
每日出拳老爷子19 分钟前
[C#] 使用TextBox换行失败的原因与解决方案:换用RichTextBox的实战经验
开发语言·c#
半桔22 分钟前
【Linux手册】从接口到管理:Linux文件系统的核心操作指南
android·java·linux·开发语言·面试·系统架构
闻缺陷则喜何志丹22 分钟前
【前缀和 BFS 并集查找】P3127 [USACO15OPEN] Trapped in the Haybales G|省选-
数据结构·c++·前缀和·宽度优先·洛谷·并集查找
nightunderblackcat31 分钟前
新手向:实现ATM模拟系统
java·开发语言·spring boot·spring cloud·tomcat·maven·intellij-idea
开开心心就好33 分钟前
电脑息屏工具,一键黑屏超方便
开发语言·javascript·电脑·scala·erlang·perl
笑衬人心。41 分钟前
Java 17 新特性笔记
java·开发语言·笔记
序属秋秋秋2 小时前
《C++初阶之内存管理》【内存分布 + operator new/delete + 定位new】
开发语言·c++·笔记·学习
ruan1145143 小时前
MySQL4种隔离级别
java·开发语言·mysql