从汇编看c++中含有虚基类对象的析构

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

从汇编看c++中含有虚基类对象的析构

当构造类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子对象的首地址
                                                                               ;这里将虚表首地址给该内存,设置第二处虚表

从汇编看c++中含有虚基类对象的析构 从汇编看c++中含有虚基类对象的析构 从汇编看c++中含有虚基类对象的析构

图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的析构含仅仅由析构代理函数调用。

原文链接: https://www.cnblogs.com/chaoguo1234/p/3220515.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/97533

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月10日 上午4:17
下一篇 2023年2月10日 上午4:19

相关推荐