可以做公众号的一些网站,wordpress页面相册,做外贸上哪些网站,wordpress主题ruikedu目录
前言
具有虚基类的对象的构造过程
通过子类的对象存取虚基类成员的实现分析 接下来我将持续更新“深度解读《深度探索C对象模型》”系列#xff0c;敬请期待#xff0c;欢迎左下角点击关注#xff01;也可以关注公众号#xff1a;iShare爱分享#xff0c;或文章末…目录
前言
具有虚基类的对象的构造过程
通过子类的对象存取虚基类成员的实现分析 接下来我将持续更新“深度解读《深度探索C对象模型》”系列敬请期待欢迎左下角点击关注也可以关注公众号iShare爱分享或文章末尾扫描二维码自动获得推文和全部的文章列表。 前言 前面几篇分析了静态数据成员、普通的数据成员以及在继承体系下的数据成员的存取效率的分析请从这里阅读 深度解读《深度探索C对象模型》之数据成员的存取效率分析一 深度解读《深度探索C对象模型》之数据成员的存取效率分析二 深度解读《深度探索C对象模型》之数据成员的存取效率分析三 接下来来分析虚继承的实现以及它的效率评测在读这篇文章之前为了能够更好地理解内容建议先阅读一下以下的文章补充一些基础知识。 深度解读《深度探索C对象模型》之默认构造函数 深度解读《深度探索C对象模型》之C对象的内存布局一 深度解读《深度探索C对象模型》之C对象的内存布局二 深度解读《深度探索C对象模型》之C虚函数实现分析一 深度解读《深度探索C对象模型》之C虚函数实现分析二 深度解读《深度探索C对象模型》之C虚函数实现分析三 深度解读《深度探索C对象模型》之C虚函数实现分析四 深度解读《深度探索C对象模型》之C对象的构造过程一 深度解读《深度探索C对象模型》之C对象的构造过程二 深度解读《深度探索C对象模型》之C对象的构造过程三 现在来分析在虚继承时访问虚基类的数据成员的实现方法以及它和访问普通的数据成员之间的效率对比评测。虚继承虽然很少使用但可能难以避免有时业务中确实需要用到这时熟悉编译器对于虚继承的实现手法和存取虚基类成员的效率这样可以对所写的代码了然于胸做到心中有数。我们以一个具体的例子来分析
class Grand {
public:virtual ~Grand() {}int g;
};
class Base1: virtual public Grand {
public:int b1;
};
class Base2: virtual public Grand {
public:int b2;
};
class Derived: public Base1, public Base2 {
public:int d;
};int main() {Derived d;d.g 5;Derived* pd d;pd-g 6;Base1* pb1 d;pb1-g 7;Base2* pb2 d;pb2-g 8;Grand* pg d;pg-g 9;return 0;
} 要深入分析编译器对虚继承的实现手法最好的方法是分析编译器生成的汇编代码上面短短的C代码生成的汇编代码却相当多不可能全部贴出来只能将有需要讲到的地方贴出来。
具有虚基类的对象的构造过程 首先main函数的第一行定义了一个Derived类的对象这里则会去调用Derived类的默认构造函数在Derived类的构造函数里首先会去调用Grand子类的默认构造函数然后调用Base1子类和Base2子类的默认构造函数最后是完成自身的构造。不要奇怪为什么会去调用这些默认构造函数明明代码中并没有定义这些函数啊如果对这个有疑问的话可以先看一下另外一篇“深度解读《深度探索C对象模型》之默认构造函数”。在这些默认构造函数里主要的事情就是去设置虚表指针因为代码中有虚继承所以编译器会生成一个虚表而且虚基类中有定义了虚函数所以它的派生类中都会继承虚函数这里指的都是虚析构函数所以也有一个虚函数表这些具体的细节不同的编译器有不同的实现手法clang和gcc是将这两个表合二为一只需要一个指针指向它们而MSVC是分开两个表所以需要两个指针来指向它们但是原理都大致相同这里就以clang的实现为例。 下面是Derived类的构造函数的汇编代码 上面汇编代码的前三行是保存上个函数的栈寄存器然后开辟了16字节的栈空间来使用。接着是将rdi寄存器的值保存到栈空间中rdi是调用Derived类构造函数时传递过来的参数它是Derived类的对象d的地址。 上面汇编代码的第66行在此地址之上偏移32个字节跳过Base1子对象和Base2子对象即为Grand类子对象的起始地址对对象的内存布局还不熟悉的可以先参考
深度解读《深度探索C对象模型》之C对象的内存布局一
深度解读《深度探索C对象模型》之C对象的内存布局二
这时将rdi寄存器对象d的地址偏移了32字节后作为参数调用Grand类的默认构造函数。下面是Grand类的默认构造函数汇编代码 汇编代码的第110行到112行在Grand类的默认构造函数里会先设置Grand类的虚函数表指针指向Grand类的虚函数表每个类都会有一个虚函数表[rip vtable for Grand]是虚表的地址以下这个表的内容 前面两行先不管它第三、四行即存放虚函数的地址。所以上面汇编代码的第111行里跳过16字节即是跳过了前面两行将第三行的内容即虚函数的地址设置给Grand类子对象的起始地址中至此完成了Grand类子对象的虚函数表的设置。 回到Derived类的构造函数的汇编代码中见第68行到71行这里是去调用Base1子类的默认构造函数 [rbp - 16] 栈空间保存的是Derived类的对象d的地址这里再加载到rdi寄存器中作为调用Base1类默认构造函数的第一个参数。第69行代码是取得“VTT for Derived”表的地址并保存在rsi寄存器中作为调用Base1类默认构造函数的第二个参数。 “VTT for Derived”表的内容如下 上面汇编代码的第70行将rsi里的值加上8的偏移值也就是上表的起始地址加上8实际上就是指向第二条内容的地址最后第71行代码调用Base1类的默认构造函数。 Base1类的默认构造函数代码 第118行、119行代码将第一个参数rdi寄存器和第二个参数rsi寄存器中的内容分别保存到栈空间[rbp - 8]和[rbp - 16]中。从上面的分析中我们知道rsi保存的内容是“construction vtable for Base1-in-Derived24”它实际上是表“construction vtable for Base1-in-Derived”的起始地址加上偏移值24的意思。那么来看下“construction vtable for Base1-in-Derived”表的内容 上面的表加上24的偏移值实际上就是跳过前面三行的内容指向第四条的内容也就是Base1类的虚析构函数的地址。然后上面汇编代码的第122行、123行将这个地址设置给Base1子对象的起始位置这个就是之前说过的设置虚函数表指针。 接下来的第124行到127行的代码意思跟前面的差不多只不过它设置的虚基类子对象的虚函数表指针。第124行的rcx 8rcx原先的内容是“VTT for Derived”表的第二行即“construction vtable for Base1-in-Derived24”这里再加8就是指向第三行并将它的内容保存到rdx寄存器中。第126行的rcx - 24实际上就是跳回到“construction vtable for Base1-in-Derived”表的起始位置然后对其取值也就是32参见上面的表并保存到rcx寄存器中。在汇编代码的第127行rax rcx表示对象d的起始地址也是Base1子对象的起始地址加上32的偏移值定位到虚基类Grand类的子对象的起始地址并将虚函数表指针设置到这个起始地址中。 接下来的Base2子对象的构造过程跟构造Base1子对象的过程类似不同的是设置的虚函数表指针的内容不同。最后是Derived类子对象的构造过程都大同小异这里就不再赘述。 通过上面的分析我们知道在构造Base1和Base2子类的时候除了设置Base1和Base2自身的虚函数表指针之外还会重新设置Grand类的虚函数表指针设置两次一次设置为指向Base1类的后一次设置为指向Base2类的最后在构造Derived类的时候全都更新为指向Derived类的虚函数表。 构造完Derived类的对象后接着来分析存取虚基类的数据成员g我们采取几种不同的途径来存取如通过Derived类的对象、Derived类型的指针、Base1和Base2父类的指针以及虚基类Grand类型的指针来存取数据成员g分别分析它们的实现手法有什么区别。
通过子类的对象存取虚基类成员的实现分析 首先通过对象来存取C代码第21行d.g 5;对应的汇编代码如下
mov rax, qword ptr [rbp - 56]
mov rax, qword ptr [rax - 24]
mov dword ptr [rbp rax - 48], 5 [rbp - 56]是对象Derived对象d的地址这个地址在构造对象d的最后阶段的时候被写入虚函数表指针
mov rax, qword ptr [rbp - 16] # 8-byte Reload
lea rcx, [rip vtable for Derived]
add rcx, 24
mov qword ptr [rax], rcx 第2行是加载虚表的地址到rcx寄存器这个虚表包含了虚基类表和虚函数表然后加上偏移值24写入到对象的起始地址中加上偏移值24后指向了虚函数的地址下面是Derived类的虚表的内容
vtable for Derived:.quad 32.quad 0.quad typeinfo for Derived.quad Derived::~Derived() [complete object destructor].quad Derived::~Derived() [deleting destructor].quad 16.quad -16.quad typeinfo for Derived.quad non-virtual thunk to Derived::~Derived() [complete object destructor].quad non-virtual thunk to Derived::~Derived() [deleting destructor].quad -32.quad -32.quad typeinfo for Derived.quad virtual thunk to Derived::~Derived() [complete object destructor].quad virtual thunk to Derived::~Derived() [deleting destructor] 这个表中有几种类型的虚函数这个主要是跟多态的调用有关主要是为了实现虚函数的多态调用这里先不分析后面再专门讲这个。接着上面的汇编代码对象d的起始地址的内容现在就是虚表的地址偏移24字节rax - 24就相当于又指向了虚表的起始地址[rax - 24]是取这个地址的内容相当于指针的解引用也就是32。rbp rax - 48相当于rbp - 56 8 raxrbp - 56是对象的起始地址加上rax即32是跳过了Base1和Base2两个子类的大小再加8是因为Grand子类的前面有一个虚函数表指针大小为8字节所以最终指向的地址为数据成员g的地址然后对其赋值为5。
未完待续。。。敬请点击左下角的关注以获得及时更新 本主页会定期更新为了能够及时获得更新敬请关注我点击左下角的关注。也可以关注公众号请在微信上搜索公众号“iShare爱分享”并关注或者扫描以下公众号二维码关注以便在内容更新时直接向您推送。