当前位置: 首页 > news >正文

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

1. 多继承代码

#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::fooAd.fooB();  // Output: Base1::fooBd.fooC();  // Output: Derived::fooCd.fooD();  // Output: Base2::fooDd.fooE();  // Output: Derived::fooEreturn 0;
}

运行结果:

$ ./layout_part4
sizeof(Base1): 16
sizeof(Base2): 16
sizeof(Derived): 32
Derived::fooA
Base1::fooB
Derived::fooC
Base2::fooD
Derived::fooE

2. GDB打印内存布局

(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)

继续查看虚表内容:

(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:

(gdb) disass 0x0000000000400b87
Dump of assembler code for function _ZThn16_N7Derived4fooCEv:0x0000000000400b87 <+0>:     sub    $0x10,%rdi0x0000000000400b8b <+4>:     jmp    0x400b5c <Derived::fooC()>

可见,是将指向Base2部分的指针向上偏移16,指向Derived对象的内存起始地址,然后跳转到Base1部分虚表里的Derived::fooC()

比如,通过基类指针来操作fooc:

Base2 *b2ptr = new Derived();

b2ptr->fooC();

将会对当前的this指针(b2ptr)向上偏移到Derived对象起始地址。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Deep-Live-Cam启动
  • Flink 流转表,表转流,watermark设置
  • pytest参数化多种用法总结
  • Python在QtSide6(PyQt)上加载网页使用OpenCV进行图像处理
  • PyQtGraph库的基本使用
  • 集合及数据结构第九节————树和二叉树
  • SSL/TLS协议信息泄露漏洞修复
  • C++初学(14)
  • 回顾前面刷过的算法(8)
  • Java-希尔排序算法介绍、应用场景和示例代码
  • spingboot实现常规增删改查
  • erlang学习:gen_server书上案例22.6练习题4
  • jmeter通过参数文件、循环组件实现多账号登陆
  • npm install 报错解决记录
  • Golang 使用redis stream实现一个实时推送功能
  • 4个实用的微服务测试策略
  • Angular 2 DI - IoC DI - 1
  • egg(89)--egg之redis的发布和订阅
  • Java 内存分配及垃圾回收机制初探
  • Java程序员幽默爆笑锦集
  • Java到底能干嘛?
  • jQuery(一)
  • PV统计优化设计
  • python 学习笔记 - Queue Pipes,进程间通讯
  • socket.io+express实现聊天室的思考(三)
  • SpringBoot几种定时任务的实现方式
  • 笨办法学C 练习34:动态数组
  • 大快搜索数据爬虫技术实例安装教学篇
  • 构建二叉树进行数值数组的去重及优化
  • 规范化安全开发 KOA 手脚架
  • 猴子数据域名防封接口降低小说被封的风险
  • 回流、重绘及其优化
  • 它承受着该等级不该有的简单, leetcode 564 寻找最近的回文数
  • 想写好前端,先练好内功
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • # 利刃出鞘_Tomcat 核心原理解析(二)
  • #Linux(权限管理)
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • #微信小程序:微信小程序常见的配置传值
  • #我与Java虚拟机的故事#连载06:收获颇多的经典之作
  • (14)学习笔记:动手深度学习(Pytorch神经网络基础)
  • (HAL库版)freeRTOS移植STMF103
  • (第二周)效能测试
  • (二)斐波那契Fabonacci函数
  • (论文阅读11/100)Fast R-CNN
  • (七)glDrawArry绘制
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (四) 虚拟摄像头vivi体验
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • ... 是什么 ?... 有什么用处?
  • .NET分布式缓存Memcached从入门到实战
  • .net分布式压力测试工具(Beetle.DT)
  • .NET中的Exception处理(C#)