从汇编看c++中含有虚基类对象的析构
c++中,当继承结构中含有虚基类时,在构造对象时编译器会通过将一个标志位置1(表示调用虚基类构造函数),或者置0(表示不调用虚基类构造函数)来防止重复构造虚基类子对象。如下图菱形结构所示:

当构造类Bottom对象时,Bottom构造函数里面的c++伪码如下(单考虑标志位,不考虑其他):
//Bottom构造函数伪码
flag = ;//标志位
if (flag) {
调用虚基类Top的构造函数
}
flag = ;//标志位清零
调用Left的构造函数
flag = ;//标志位清零
调用Right的构造函数
其他操作 //Left构造函数伪码
if (flag) {
调用虚基类Top的构造函数
}
其他操作 //right构造函数伪码:
if (flag) {
调用虚基类Top的构造函数
}
其他操作
编译器通过这种方式,保证虚基类的构造函数不会重复调用,那么析构的时候,是不是也是通过这种标志位的方式呢?下面来看c++源码:
class Top {
private:
int _top;
public:
Top(int top = ) : _top(top) {}
virtual ~Top() {}
virtual int get1() {
return ;
}
};
class Left : virtual public Top {
private:
int _left;
public:
Left(int left = ) : _left(left) {}
virtual ~Left() {}
virtual int get2() {
return ;
}
};
class Right : virtual public Top {
private:
int _right;
public:
Right(int right = ) : _right(right) {}
virtual ~Right() {}
virtual int get3() {
return ;
}
};
class Bottom : public Left, public Right {
private:
int _bottom;
public:
Bottom(int bottom = ) : _bottom(bottom) {}
virtual ~Bottom() {}
virtual int get4() {
return ;
}
};
int main() {
Bottom b;
}
上面类之间的继承关系是一个菱形继承,下面就来看一下析构的时候汇编码。
析构的时候,并不直接调用Bottom的析构函数,而是先调用的析构代理函数,其部分相关的汇编码如下:
lea ecx,[b];将对象b的首地址给寄存器ecx
012814BD call Bottom::`vbase destructor' (1281019h);调用析构代理函数
析构代理函数函数的汇编码如下(只列出相关部分)
mov dword ptr [ebp-],ecx ;寄存器ecx里面存放对象b首地址(this指针,即对象b首地址),将ecx的值给ebp-8所代表的的内存
01281C73 mov ecx,dword ptr [this];将this指针给寄存器ecx
01281C76 add ecx,1Ch;ecx里面的值加28byte,调整this指针,所指位置如图1所示
01281C79 call Bottom::~Bottom (1281055h) ;调用类Bottom的析构函数
01281C7E mov ecx,dword ptr [this];this指针给寄存器ecx
01281C81 add ecx,1Ch;ecx里面的 值加28byte,调整this指针,所指位置如图4所示
01281C84 call Top::~Top (1281096h) ;调用虚基类Top的析构函数
在析构代理函数里面先调用了Bottom的析构函数,然后调用虚基类Top的析构函数
Bottom的析构函数汇编码如下(只列出相关部分):
mov dword ptr [ebp-14h],ecx ;寄存器ecx里面存有this指针,所指位置如上图1,将其值存到ebp-14h所代表的内存中
00C13C52 mov eax,dword ptr [ebp-14h];将ebp-14h所代表的内存里面的值(this指针)给寄存器eax
00C13C55 mov dword ptr [eax-1Ch],offset Bottom::`vftable' (0C16758h);虚表首地址给向上偏移this指针28byte处内存,即对象b首地址处(设置第一处虚表)
00C13C5C mov eax,dword ptr [ebp-14h];将ebp-14h所代表的内存里面的值(即对象b的首地址)给寄存器eax
00C13C5F mov dword ptr [eax-10h],offset Bottom::`vftable' (0C1674Ch);虚表首地址给向上偏移this指针16byte处内存,即父类Right子对象首地址(设置第二处虚表)
00C13C66 mov eax,dword ptr [ebp-14h];将ebp-14h所代表的内存里面的值(即this指针)给寄存器eax
00C13C69 mov ecx,dword ptr [eax-18h];将向上偏移this指针24byte处内存内容(即vbtable首地址)给寄存器ecx
00C13C6C mov edx,dword ptr [ecx+];将偏移vbtable首地址4byte处内存内容(即vbtable指针偏移虚基类Top子对象首地址的偏移量)给寄存器edx
00C13C6F mov eax,dword ptr [ebp-14h];将ebp-14h所代表的内存里面的值(即对象b的首地址)给寄存器eax
00C13C72 mov dword ptr [eax+edx-18h],offset Bottom::`vftable' (0C16740h);eax为this指针,edx为刚获得的偏移量 eax+edx-18h
;这里将虚表首地址给该内存(设置第三处虚表)
00C13C7A mov dword ptr [ebp-4],0
00C13C81 mov ecx,dword ptr [ebp-14h] ;将ebp-14h所代表的内存里面的值(即对象b的首地址)给寄存器ecx
00C13C84 sub ecx,4 ;ecx里面的值减4,调整this指针,此时this指针所指位置如图2
00C13C87 call Right::~Right (0C11091h);调用父类Right子对象的析构函数
00C13C8C mov dword ptr [ebp-4],0FFFFFFFFh
00C13C93 mov ecx,dword ptr [ebp-14h];将ebp-14h所代表的内存里面的值(即this指针)给寄存器ecx
00C13C96 sub ecx,10h ;ecx里面的值减16,调正this指针,this指针所指位置如图3
00C13C99 call Left::~Left (0C110DCh);调用父类Left子对象的析构函数 01281C20 mov dword ptr [ebp-8],ecx;ecx里面存放this指针,所指位置如图2,将其存放到 ebp-8所代表的内存
01281C23 mov eax,dword ptr [this];将this指针给寄存器eax
01281C26 mov dword ptr [eax-0Ch],offset Right::`vftable' (1286874h);将虚表首地址给向上偏移this指针12byte处内存,设置第一处虚表
01281C2D mov eax,dword ptr [this];将this指针给寄存器eax
01281C30 mov ecx,dword ptr [eax-] ;将向上偏移this指针8byte处内存内容(即vbtable首地址)给寄存器ecx
01281C33 mov edx,dword ptr [ecx+];将偏移vbtable首地址4byte处内存内容(即vbtable指针偏移虚基类Top首地址的偏移量),给寄存器edx
01281C36 mov eax,dword ptr [this] ;将this指针给寄存器eax
01281C39 mov dword ptr [eax+edx-],offset Right::`vftable' (1286814h) ;eax是this指针,edx是偏移量,因此eax+edx-8即使虚基类Top子对象的首地址
;这里将虚表首地址给该内存,设置第二处虚表

图1 图2 图3
在Bottom的析构函数里面,首先调用了Right的析构函数,然后调用了Left的析构函数。并且在调用这些函数之前,设置好了相关的虚表。
下面是Right析构函数的汇编码(只列出相关部分):
01281C20 mov dword ptr [ebp-8],ecx;ecx里面存放this指针,所指位置如图2,将其存放到 ebp-8所代表的内存
01281C23 mov eax,dword ptr [this];将this指针给寄存器eax
01281C26 mov dword ptr [eax-0Ch],offset Right::`vftable' (1286874h);将虚表首地址给向上偏移this指针12byte处内存,设置第一处虚表
01281C2D mov eax,dword ptr [this];将this指针给寄存器eax
01281C30 mov ecx,dword ptr [eax-8] ;将向上偏移this指针8byte处内存内容(即vbtable首地址)给寄存器ecx
01281C33 mov edx,dword ptr [ecx+4];将偏移vbtable首地址4byte处内存内容(即vbtable指针偏移虚基类Top首地址的偏移量),给寄存器edx
01281C36 mov eax,dword ptr [this] ;将this指针给寄存器eax
01281C39 mov dword ptr [eax+edx-8],offset Right::`vftable' (1286814h) ;eax是this指针,edx是偏移量,因此eax+edx-8即使虚基类Top子对象的首地址
;这里将虚表首地址给该内存,设置第二处虚表
在Right函数中并没有调用虚基类Top的析构函数,函数只是一开始的时候,设置好了相关的虚表。
下面是Left函数析构函数的汇编码(值列出相关部分):
mov dword ptr [ebp-],ecx;ecx里面存放的this指针,所指位置如图3,将其存放到 ebp-8所代表的内存里面
mov eax,dword ptr [this];将this指针给寄存器eax
mov dword ptr [eax-0Ch],offset Left::`vftable' (1286794h);将虚表首地址给向上偏移this指针12byte处内存,设置第一处虚表
0128188D mov eax,dword ptr [this] ;将this指针给寄存器eax
01281890 mov ecx,dword ptr [eax-8] ;将向上偏移this指针8byte处内存内容(即vbtable首地址)给寄存器ecx
01281893 mov edx,dword ptr [ecx+4];将偏移vbtable首地址4byte处内存内容(即vbtable指针偏移虚基类Top首地址的偏移量),给寄存器edx
01281896 mov eax,dword ptr [this];将this指针给寄存器eax
01281899 mov dword ptr [eax+edx-8],offset Left::`vftable' (1286788h);eax是this指针,edx是偏移量,因此eax+edx-8即使虚基类Top子对象的首地址
;这里将虚表首地址给该内存,设置第二处虚表
在Left函数里面也没有调用虚基类Top的析构函数,函数只是一开始的时候,设置好了相关的虚表
最后是虚基类Top的析构函数(只列出相关部分):
012816D0 mov dword ptr [ebp-],ecx;寄存器ecx里面存有this指针,所指位置如图1所示 ,将this指针的值存入ebp-8所代表的内存
012816D3 mov eax,dword ptr [this];将this指针给寄存器eax
012816D6 mov dword ptr [eax],offset Top::`vftable' (128677Ch);将虚表首地址给this指针所指向的内存
通过上面的汇编码,可以发现,菱形结构中的析构函数并没有使用构造函数中的标记来防止重复析构,而是将虚基类Top的析构函数放到最后调用。在调用Left和Right的析构函数时,根本不调用虚基类Top的析构函数。虚基类Top的析构含仅仅由析构代理函数调用。
从汇编看c++中含有虚基类对象的析构的更多相关文章
- C++派生类中如何初始化基类对象(五段代码)
今天收到盛大的面试,问我一个问题,关于派生类中如何初始化基类对象,我在想派生类对于构造函数不都是先构造基类对象,然后在构造子类对象,但是如果我们在成员初始化列表先初始化派生类的私有成员,在函数内去调用 ...
- C++中 引入虚基类的作用
当某类的部分或全部直接基类是从另一个基类共同派生而来时,这直接基类中,从上一级基类继承来的成员就拥有相同的名称,派生类的对象的这些同名成员在内存中同时拥有多个拷贝,同一个函数名有多个映射.可以使用作用 ...
- C++调用动态库中的虚基类成员函数时总是进错函数
原创文章,转载请注明作者与本文原始URL. 问题描述:最近遇到这样一个问题,在调用C++的一个成员函数时,总是进错函数.在调用 pMsg->GetMsgContent() 的时候,总是进入到 p ...
- 从汇编看c++中的虚拟继承及内存布局(二)
下面是c++源码: class Top {//虚基类 public: int i; Top(int ii) { i = ii; } virtual int getTop() { cout <&l ...
- C++中虚基类在派生类中的内存布局
今天重温C++的知识,当看到虚基类这点的时候,那时候也没有太过追究,就是知道虚基类是消除了类继承之间的二义性问题而已,可是很是好奇,它是怎么消除的,内存布局是怎么分配的呢?于是就深入研究了一下,具体的 ...
- 详解C++中基类与派生类的转换以及虚基类
很详细!转载链接 C++基类与派生类的转换在公用继承.私有继承和保护继承中,只有公用继承能较好地保留基类的特征,它保留了除构造函数和析构函数以外的基类所有成员,基类的公用或保护成员的访问权限在派生类中 ...
- C++ 虚基类的定义、功能、规定
原文声明:http://blog.sina.com.cn/s/blog_93b45b0f01011pkz.html 虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承的,虽然 ...
- C++学习20 虚基类详解
多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如非常经典的菱形继承层次.如下图所示: 类A派生出类B和类C,类D继承自类B和类 ...
- RTTI、虚函数和虚基类的实现方式、开销分析及使用指导(虚函数的开销很小,就2次操作而已)
白杨 http://baiy.cn “在正确的场合使用恰当的特性” 对称职的C++程序员来说是一个基本标准.想要做到这点,首先要了解语言中每个特性的实现方式及其开销.本文主要讨论相对于传统 C 而言, ...
随机推荐
- vc2015 编译libcurl带openssl
1.先编译zlib下载地址 http://zlib.net/ 我这边vc2015编译需要配置环境变量,不知道是装了wdk的原因还是多个vc版本的原因 设置环境变量lib和include路径 INCLU ...
- 怎么在网页中加入ICO图标
1.首先制作一个16x16的icon图标,命名为cssbbs.ico(这里的名字可以随便改!),放在根目录下.2.然后将下面的代码嵌入head区:<link rel="icon&quo ...
- Lambda表达式 之 C#
Lambda表达式 "Lambda表达式"是一个匿名函数,是一种高效的类似于函数式编程的表达式,Lambda简化了开发中需要编写的代码量.它可以包含表达式和语句,并且可用于创建委托 ...
- [linux] linux知识积累(不断更新中…)
一.vim知识 f(find)命令也可以用于移动,fx将找到光标后第一个为x的字符,3fd将找到第三个为d的字符. D 删除当前字符至行尾.D=d$ :split或new 打开一个新窗口,光标停在顶层 ...
- .net mvc结合微软提供的FormsAuthenticationTicket登陆
一.Web.config <system.web> <compilation debug="true" targetFramework="4.5&quo ...
- 剖析C语言中a=a+++++a的无聊问题
同僚们闲聊,突然就聊到了a+++++a的问题.这种纯属C语言 “二” 级的问题应该是从a+++a引申出来的吧.于是乎兄弟姐妹们开始讨论它的运算结果,以及改如何理解.更有人写出(a++)+(++a) a ...
- SIM卡基础,各管脚意义,封装定义
1. SIM简介 SIM卡(Subscriber Identity Module).即用户识别模块,是一张符合GSM规范的"智慧卡".SIM卡可以插入任何一部符合GSM规范的移动电 ...
- ANDROID使用MULTIPARTENTITYBUILDER实现类似FORM表单提交方式的文件上传
最近在做 Android 端文件上传,要求采用 form 表单的方式提交,项目使用的 afinal 框架有文件上传功能,但是始终无法与php写的服务端对接上,无法上传成功.读源码发现:afinal 使 ...
- UML--建模
建模公式 这种精华的东西,一定是值得研读和实践的! myself:人,事,物,规则. 人,业务主角.业务工人.参与者.如果应用到教务系统中,就是管理员,主任,老师的关系. 事,业务用例,系统用例. 物 ...
- 去掉java反编译(JD-GUI)生成的源文件中注释
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.Fi ...