从汇编看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 而言, ...
随机推荐
- C#拖动自己的定义标题栏(panel)以及实现窗体拖动关闭和最小化
//没有标题 this.FormBorderStyle = FormBorderStyle.None; //任务栏不显示 this.ShowInTaskbar = false; //实现拖动 1.在窗 ...
- vi 编辑器初步
vi 编辑器初步 4,vi进入后是命令模式 ,可以用i o s 进入插入模式 i ,在当前字符位置插入,o为新开一行插入,s删除当前字符添加 5,r 为直接替换当前字符 6,到行头按0,$为到行尾到未 ...
- 观《Terminal》之感
读书笔记系列链接地址http://www.cnblogs.com/shoufengwei/p/5714661.html. 经人推荐,用了几天时间欣赏了这部斯皮尔伯格导演的电影<Te ...
- 前端面试题之js篇
前端面试也可为是鱼龙混杂,各公司面试题的种类也大不相同,有的公司注重基础语法,面试题偏于ES,有的公司偏于页面逻辑,会考差一些js的应用,现将遇到过的题和典型的题整理一下. 1. 0.2-0.1 == ...
- Android之SharedPreferences两个工具类
相信Android的这个最简单的存储方式大家都很熟悉了,但是有一个小小技巧,也许你没有用过,今天就跟大家分享一下,我们可以把SharedPreferences封装在一个工具类中,当我们需要写数据和读数 ...
- yii第一个应用blog
1. 连接到数据库 大多数 Web 应用由数据库驱动,我们的测试应用也不例外.要使用数据库,我们首先需要告诉应用如何连接它.修改应用的配置文件 WebRoot/testdrive/protected/ ...
- Dynamics CRM 2013 初体验(2):UI
Dynamics CRM 2013 系统的UI与2011相比改动是巨大的:传统的导航栏被去掉了,取代它的是win8风格的小磁铁:Ribbon风格的工具栏也被去掉啦,它的风格将回滚至4.0时代:新系统添 ...
- hibernate中load,get;find,iterator;merge,saveOrUpdate,lock的区别
hibernate中load,get;find,iterator;merge,saveOrUpdate,lock的区别 转自http://www.blogjava.net/bnlovebn/archi ...
- RedHat安装GCC问题-解决依赖问题
RedHat Linux在安装gcc时需要cpp和cloog-ppl但是在安装cpp的时候需要这个依赖:libmpfr.so.1()(64bit) is needed by cpp-4.4.6-3.e ...
- 第04讲- Android项目目录结构分析
学习内容: 1. 认识R类(R.java)的作用 R.java是在建立项目时自动生成的,这个文件是只读模式,不能更改.R类中包含很多静态类,且静态类的名字都与res中的一个名字对应,即R ...