C++虚函数的实现细节、虚析构函数 汇编解析

  C++里多态的实现,依靠的是虚函数的运行时函数地址确定,不过真正的实现过程,还是在编译阶段。编译器究竟对虚函数做了怎样的处理?这就是本文所描述的。然后又对虚函数中最特殊的虚析构函数的运行情况进行了分析。
   1、类的存储空间
   在INTEL 32 CPU,VC6环境下,空类的一个实例占一个字节(特例);
   一个C++类本身(注意:不是对象),在内存里是有信息的, 比如虚函数表、静态成员变量。
   函数虚函数的类,它的每一个对象实例,在内存中,头四个字节存放的都是虚函数表指针。
   2、虚函数的实现过程
   对虚函数,编译器不给出直接的函数调用地址,而是关于一个未知量的表达式,这个参数就是虚函数表的指针。使用虚函数实现C++多态的方法,网上很多讲解,这里不多讲,本文结合源代码和部分汇编代码,分析了编译过程中对虚函数的处理。
   3、虚析构函数
   无论基类的析构函数是否为虚析构函数. 基类的析构函数总是会被自动调用的;但是, 如果用基类指针去操作一个了派生类对象,那么在delete这个基类指针时,派生类的析构函数将不会被调用。
   4. 本文为原创,转载或其他用途请注明出处:http://blog.csdn.net/ydbcsdn/archive/2008/10/24/3137384.aspx

// VC测试代码
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <stdio.h>

class Base;
class Derived;
void GFunction(void);

int main(int argc, char* argv[])
{
    GFunction();

    char a = 127;
    a+=1;
    printf("new a = %d/n", a);

    getchar();
    return 0;
}

class Base
{
public:
    Base::Base()
    {
    };
    virtual Base::~Base()
    {
        printf("Base deconstruct/n");
    };
    virtual void Fun()
    {
    };
    int a ;
};

class Derived : public Base 
{
public:
    Derived::Derived()
    {
    };
    virtual Derived::~Derived()
    {
        printf("Derived deconstruct/n");
    };
    virtual void Fun()
    {
    };
};

void GFunction(void)
{
    printf("Class Base    Sizeof =%d /n", sizeof(Base));
    printf("Class Derived Sizeof =%d /n/n", sizeof(Derived));

    Base* pA = (Base*)new Derived;
    pA->Fun();  // 虚函数调用

    delete pA;
}
  pA->Fun()的汇编代码如下:

59:       pA->Fun();
0040D75C       mov         edx,dword ptr [ebp-10h]  // edx为pA

0040D75F       mov         eax,dword ptr [edx]      // eax为pA对象的虚表指针pVTable
0040D761       mov         esi,esp
0040D763       mov         ecx,dword ptr [ebp-10h]  // this指针存入ecx

0040D766       call        dword ptr [eax+4]        // 函数地址:虚表指针+4, 就是虚表中第二项

0040D769       cmp         esi,esp
0040D76B       call        __chkesp (00401b20)
   1. 如果成员函数不是虚函数,那么编译的时候,就直接指定了调用函数的入口;
   2. 如果是虚函数,那么编译时不直接指定函数入口,而是先在对象的内存空间里取一个值(这个值就是虚函数表的地址,放在对象内存空间的最前面4个字节里)。汇编代码中会有取值的过程;
   3. 虚函数表中按顺序存放着虚函数在地址空间中的地址:的第一个DWORD存储的就是第一个虚函数的地址,第二个DWORD存储的就是第二个虚函数的地址;
   3. 编译器在编译过程中已经知道你调的那个函数在虚函数表中的序号。汇编代码中会有体现;
   4. 在运行时,就能正确找到调用函数的地址,并调用它.
  
     析构函数的一点补充:
在一个项目中,如果有N层派生类,编译器总是保证所有基类的析构函数都被依次调用,但问题是,究竟从那层开始调用呢?对于非虚析构函数,显然是在编译期间就直接确定的,对虚析构函数,在运行时,才能确定是从哪一层开始往下层调用(基类)。 
  事实上,和一般虚函数一样,运行时,才确定要调用的析构函数,不过有些不同的是,析构函数执行完后,下一条指令就是基类析构函数的CALL指令,一直到最上层为止。 

下面是一段debug下的反汇编,其中Base派生自BaseBase.可以看到~Base调用后,会自动调用~BaseBase 
    virtual Base::~Base() // 基类析构函数 
    { 
00401740  push        ebp  
00401741  mov        ebp,esp 
00401743  sub        esp,0CCh 
00401749  push        ebx  
0040174A  push        esi  
0040174B  push        edi  
0040174C  push        ecx  
0040174D  lea        edi,[ebp-0CCh] 
00401753  mov        ecx,33h 
00401758  mov        eax,0CCCCCCCCh 
0040175D  rep stos    dword ptr es:[edi] 
0040175F  pop        ecx  
00401760  mov        dword ptr [ebp-8],ecx 
00401763  mov        eax,dword ptr [this] 
00401766  mov        dword ptr [eax],offset Base::`vftable' (44533Ch) 
        printf("Base deconstruct/n"); 
0040176C  push        offset string "Base deconstruct/n" (445344h) 
00401771  call        printf (405760h) 
00401776  add        esp,4 
    }; 
00401779  mov        ecx,dword ptr [this] 
0040177C  call        BaseBase::~BaseBase (4017A0h)  // 上层基类也紧跟着调用了


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ydbcsdn/archive/2008/10/24/3137384.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值