当前位置: 首页 > Linux

深入理解C++对象模型(入门)--从虚继承说起

时间:2023-04-06 05:49:45 Linux

0前言最近工作之余,拿起《深入探究C++对象模型》一书,想进一步温习一下并理解C++对象模型的相关原理。以前看这本书,只是停留在表面,并没有去体会和验证书中的内容。所以这一次,我想既然又要学习了,就深入了解一下目前流行的编译器是如何实现C++对象模型的。因此这个话题。目前广泛使用的编译器有gcc、clang、msvc,但是作为C++后台开发,我相信gcc和clang用的比较多,我也是两个都用,所以本篇重点介绍gcc编译器,主要针对in-深入研究其C++对象模型的实现。如果你觉得深入探索C++对象模型深化了自己的内功,想进一步探究其现代编译器的实现,进一步加深理解,那么希望本专题对你有所帮助。学习本课题需要先学习理解计算机系统(原书第三版)。主要熟悉ATT汇编、基本函数调用规则等,还需要学习对C++对象模型的深入探索。这本书毕竟是这本书的主体。另外,你需要熟悉GDB,尤其是下面的命令。详细解释请参考后续链接:setprintprettyonsetprintvtblonsetprintobjecton。希望大家可以经常使用以下两个在线工具:https://cppinsights。io/godblot本题使用的机器环境如下uname-aLinuxqls-VirtualBox5.11.0-38-generic#42~20.04.1-UbuntuSMPTueSep2820:41:07UTC2021x86_64x86_64x86_64GNU/Linuxgcc编译设备版本为gccversion9.3.0(Ubuntu9.3.0-17ubuntu1~20.04)。最后,我只是因为兴趣而想进一步学习。中间可能有一些误解,欢迎大家随时指正;同时,由于工作繁忙,所以我可能会断断续续更新这个专栏。1故事的开头关于C++对象模型,我们先从《深入探究C++对象模型》第三章开头给出的例子说起。例子如下main(){std::cout<<"sizeof(X):"<}y=warning:can'tfindlinkersymbolforvirtualtablefor‘Y'value{={},Y的成员:_vptr.Y=0x7ffff7d98fc8<__exit_funcs_lock>}z=警告:找不到“Z”值警告的虚拟表的链接器符号:找到“A::A()'代替{=<无效地址>,Z的成员:_vptr.Z=0x555555555430<__libc_csu_init>}a={={=<无效地址>,Y的成员:_vptr.Y=0x0--键入获取更多,q退出,c继续而不分页--},={Z的成员:_vptr.Z=0x5555555550e0<_start>},<无数据字段>}因此可以发现gdb的结果:安装了对象y编译器安装了一个_vptr.Y成员,对象z有一个编译器安装的_vptr.Z成员,对象a有一个编译器安装的_vptr.Y和_vptr.Z成员。虽然y、z、a都有成员X,但是他们都在内存布局的开头,所以1个字节被gcc优化了,这也符合《深入探索C++对象模型》中所说的。对于空虚基类,一些新的编译器会对其进行特殊处理。在这种策略下,一个空的虚基类被认为是派生类对象开头的一部分,也就是说,他没有花费任何额外的空间,节省了1个字节。因此,可以输出相应的结果。或许故事的开头可以到这里,但是我们说到汇编层面去仔细看看现代编译器是如何实现C++对象模型的,你可能会发现DeepExplorationof中绘制的对象模型示意图C++对象模型已经满足不符合现代编译器实现?(至少GCC)所以,有以下问题:上面的vptr指针指向哪里?对应的vtbl中的内容是什么?DeepExplorationC++ObjectModel中的下图是否需要更正?在下面的示意图中,本文示例中的虚基类偏移是否正确?里面的内容是什么?带着以上的疑问,我们进入下一节,仔细看看vtbl!3细看vtbl往往表面看起来很简单,但背后却异常复杂,就像本文给出的例子一样。我们可以从编译层面了解其背后的复杂性!对于本文的例子,我们使用文章开头介绍的工具CompilerExplorer-C++(x86-64gcc(trunk))查看对应的汇编实现,并回答总结末尾提出的问题。根据CompilerExplorer-C++(x86-64gcc(trunk)),在汇编级别有以下关键符号:Y::Y()[baseobjectconstructor]\Y::Y()[completeobjectconstructor]\Z::Z()[基础对象构造函数]\Z::Z()[完整对象构造函数]\A::A()[完整对象构造函数]\A的vtable\A的VTT\Y的构造vtablein-A\构造vtableforZ-in-A\vtableforZ\VTTforZ\vtableforY\VTTforY\以及相关符号的各种typeinfo关于以上符号的含义,我会一一解释在后续的分享中。让我们在下面的摘要中回答我们的问题!vptr指向哪里(vtbl中的内容是什么?)这里,两个问题合二为一了。如果你研究过C++对象模型的深入探索,你就会明白vptr指向vtbl,而只是具体指向vtbl的槽。可能没有正确答案。本文将告诉您vptr在gcc编译器中指向哪些插槽。这里只说明Y类型的vtbl的相关结论,Y的vtbl里面有什么可以通过对应的在线工具CompilerExplorer-C++(x86-64gcc(trunk))找到,其内容如下Y的vtable:.quad0.quad0.quadY的类型信息通过Y的构造函数实现movq如下movq%rdi,-8(%rbp)movl$vtableforY+24,%edxmovq-8(%rbp),%raxmovq%rdx,(%rax)//this->vptr=*rax结合了上面两个,知道在gcc编译器下,Y的内存模型如下,所以对象模型也回答了上面关于深入探索C++对象模型的部分是给的图是否正确的问题。通过分析汇编代码,我们知道在现代的gcc编译器中,vptr并不是直接指向typeinfo,而是指向其下一个位置的slot。然后就是最后一个问题,C++对象模型的深入探索。在下面的示意图中,本文示例中的虚基类偏移是否正确?里面的内容是什么?答案很明显。在现代gcc编译器下,上图是错误的。vptr应该指向typeinfo所在槽的下一个槽。如果有虚基类,vtbl中就会多出两个槽,对于对象y这两个槽中值大于0的两个槽分别表示:top_offset,vbase_offset所谓top_offset就是vptr的距离从对象的起始位置;所谓vbase_offset就是对象中到虚基类对象的距离。距对象起始位置的距离。所以最终在现代gcc编译器下,Y的对象模型结构如下,对应对象Z的内存模型可以类比为Y,这里不展开!4小结通过本文,我们可以初步得出以下结论:如果一个类A虚继承了一个类X,那么在gcc编译场景下,类Y会生成一个对应的vtbl,并被编译器插入到一个虚表中vptrclassvtbl默认有三个slot,分别是vbase_offset,top_offset,typeinfoforAclassA的vptr会指向下一个slotoftypeinfoforAslot本文未解决的问题:什么是VTT?为什么gcc编译器会为棱柱继承生成VTT?baseobjectconstructor]和completeobjectconstructorclassA的内存模型是什么?A级施工流程是怎样的?深入理解C++对象模型的故事由来已久。本文只是一个初步的开始,将不断完善和解答以上未解决的问题。参考:使用gdb调试-使用不同语言的gdb检查数据\