从汇编看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 而言, ...
随机推荐
- Hbase常见异常
1. HBase is able to connect to ZooKeeper but the connection closes immediately hbase(main):001:0> ...
- java生产者消费者并发协作
随着职务转变,代码荒废很久了,很多时间都是在沟通需求,作为一名技术员,不写代码就感觉是在自废武功,慢慢颓废了很多,今天重新回顾了下JAVA线程知识,基础知识就不梳理了,网上也很多,主要关键几个状态位( ...
- 安装 adobe flash player
安装方法: 1. 下载Adobe Flash Player: http://fpdownload.macromedia.com/get/flashplayer/pdc/11.2. ...
- Codeforces Round #281 (Div. 2) 解题报告
题目地址:http://codeforces.com/contest/493 A题 写完后就交了,然后WA了,又读了一遍题,没找出错误后就开始搞B题了,后来回头重做的时候才发现,球员被红牌罚下场后还可 ...
- 深度学习word2vec笔记之应用篇
好不容易学了一个深度学习的算法,大家是否比较爽了?但是回头想想,学这个是为了什么?吹牛皮吗?写论文吗?参加竞赛拿奖吗? 不管哪个原因,都显得有点校园思维了. 站在企业的层面,这样的方式显然是不符合要求 ...
- 深度学习word2vec笔记之算法篇
深度学习word2vec笔记之算法篇 声明: 本文转自推酷中的一篇博文http://www.tuicool.com/articles/fmuyamf,若有错误望海涵 前言 在看word2vec的资料 ...
- Keil C51怎样将子程序段定位在固定的地址位?
以下2问题均要用C51解决1.怎样将1个子程序段定位在1个固定的地址位置?例如将 INT BCD2HEX(INT XX)定位在1000H2.如何在EEPROM 中固定的位置存放1字符串?如在200H处 ...
- logstash 字段类型转换后 需要刷新
filter { grok { match => [ "message" , "\s*%{IPORHOST:clientip}\s+\-\s+\-\s+\[%{HT ...
- cf486A Calculating Function
A. Calculating Function time limit per test 1 second memory limit per test 256 megabytes input stand ...
- JQuery轻量级网页编辑器 选中即可编辑
目前流行的可视化网页编辑器非常多,像ckeditor.kindeditor.tinyeditor等,虽然功能都非常强大,但是体积都比 较庞大,使用起来也不是很方便.今天我们分享一款基于jQuery的轻 ...