从汇编看c++中含有虚基类对象的析构
添加时间:2013-7-29 点击量:
c++中,当持续布局中含有虚基类时,在机关对象时编译器会经由过程将一个标记地位1(默示调用虚基类机关函数),或者置0(默示不调用虚基类机关函数)来防止反复机关虚基类子对象。如下图菱形布局所示:
当机关类Bottom对象时,Bottom机关函数里面的c++伪码如下(单推敲标记位,不推敲其他):
//Bottom机关函数伪码
flag = 1;//标记位
if (flag) {
调用虚基类Top的机关函数
}
flag = 0;//标记位清零
调用Left的机关函数
flag = 0;//标记位清零
调用Right的机关函数
其他操纵
//Left机关函数伪码
if (flag) {
调用虚基类Top的机关函数
}
其他操纵
//right机关函数伪码:
if (flag) {
调用虚基类Top的机关函数
}
其他操纵
编译器经由过程这种体式格式,包管虚基类的机关函数不会反复调用,那么析构的时辰,是不是也是经由过程这种标记位的体式格式呢?下面来看c++源码:
class Top {
private:
int _top;
public:
Top(int top = 0) : _top(top) {}
virtual ~Top() {}
virtual int get1() {
return 1;
}
};
class Left : virtual public Top {
private:
int _left;
public:
Left(int left = 0) : _left(left) {}
virtual ~Left() {}
virtual int get2() {
return 2;
}
};
class Right : virtual public Top {
private:
int _right;
public:
Right(int right = 0) : _right(right) {}
virtual ~Right() {}
virtual int get3() {
return 3;
}
};
class Bottom : public Left, public Right {
private:
int _bottom;
public:
Bottom(int bottom = 0) : _bottom(bottom) {}
virtual ~Bottom() {}
virtual int get4() {
return 4;
}
};
int main() {
Bottom b;
}
上方类之间的持续关系是一个菱形持续,下面就来看一下析构的时辰汇编码。
析构的时辰,并不直接调用Bottom的析构函数,而是先调用的析构函数,其项目组相干的汇编码如下:
lea ecx,[b];将对象b的首地址给存放器ecx
012814BD call Bottom::`vbase destructor (1281019h);调用析构函数
析构函数函数的汇编码如下(只列出相干项目组)
mov dword ptr [ebp-8],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+4];将偏移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-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子对象的首地址
;这里将虚表首地址给该内存,设置第二处虚表
图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函数析构函数的汇编码(值列出相干项目组):
01281880 mov dword ptr [ebp-8],ecx;ecx里面存放的this指针,所指地位如图3,将其存放到 ebp-8所代表的内存里面
01281883 mov eax,dword ptr [this];将this指针给存放器eax
01281886 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-8],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++中,当持续布局中含有虚基类时,在机关对象时编译器会经由过程将一个标记地位1(默示调用虚基类机关函数),或者置0(默示不调用虚基类机关函数)来防止反复机关虚基类子对象。如下图菱形布局所示:
当机关类Bottom对象时,Bottom机关函数里面的c++伪码如下(单推敲标记位,不推敲其他):
//Bottom机关函数伪码
flag = 1;//标记位
if (flag) {
调用虚基类Top的机关函数
}
flag = 0;//标记位清零
调用Left的机关函数
flag = 0;//标记位清零
调用Right的机关函数
其他操纵
//Left机关函数伪码
if (flag) {
调用虚基类Top的机关函数
}
其他操纵
//right机关函数伪码:
if (flag) {
调用虚基类Top的机关函数
}
其他操纵
编译器经由过程这种体式格式,包管虚基类的机关函数不会反复调用,那么析构的时辰,是不是也是经由过程这种标记位的体式格式呢?下面来看c++源码:
class Top {
private:
int _top;
public:
Top(int top = 0) : _top(top) {}
virtual ~Top() {}
virtual int get1() {
return 1;
}
};
class Left : virtual public Top {
private:
int _left;
public:
Left(int left = 0) : _left(left) {}
virtual ~Left() {}
virtual int get2() {
return 2;
}
};
class Right : virtual public Top {
private:
int _right;
public:
Right(int right = 0) : _right(right) {}
virtual ~Right() {}
virtual int get3() {
return 3;
}
};
class Bottom : public Left, public Right {
private:
int _bottom;
public:
Bottom(int bottom = 0) : _bottom(bottom) {}
virtual ~Bottom() {}
virtual int get4() {
return 4;
}
};
int main() {
Bottom b;
}
上方类之间的持续关系是一个菱形持续,下面就来看一下析构的时辰汇编码。
析构的时辰,并不直接调用Bottom的析构函数,而是先调用的析构函数,其项目组相干的汇编码如下:
lea ecx,[b];将对象b的首地址给存放器ecx
012814BD call Bottom::`vbase destructor (1281019h);调用析构函数
析构函数函数的汇编码如下(只列出相干项目组)
mov dword ptr [ebp-8],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+4];将偏移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-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子对象的首地址
;这里将虚表首地址给该内存,设置第二处虚表
图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函数析构函数的汇编码(值列出相干项目组):
01281880 mov dword ptr [ebp-8],ecx;ecx里面存放的this指针,所指地位如图3,将其存放到 ebp-8所代表的内存里面
01281883 mov eax,dword ptr [this];将this指针给存放器eax
01281886 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-8],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的析构含仅仅由析构函数调用。
原来,再大的房子,再大的床,没有相爱的人陪伴,都只是冰冷的物质。而如果身边有爱人陪伴,即使房子小,床小,也觉得无关紧要,因为这些物质上面有了爱的温度,成了家的元素。—— 何珞《婚房》#书摘#