c++多重继承下虚函数的this指针问题

          今天偶然发现一个很有意思的问题,在vc编译器想检查有没有内存泄露,于是在一个类的构造函数和析构函数各下一个断点,追踪特定分配出来的一个对象实例有没有析构。却发现无论如何都没有析构,但是使用vld内存检测工具却没有发现内存泄露,到底有没有析构呢?

         原先我用的方法是直接下断点, 在监视器里看this的值,发现根本找不到对应的this被析构。有点抓狂,后来怕出错,把this赋给一个变量,打印到输出窗口里,却发现一一配对,构造和析构,不存在泄露问题!。更抓狂了,怎么回事呢?

         断点在析构函数,

      ~some()

     {

        void * pThis=this;

    }

    这下看清楚了,鼠标晃上去或者在监视窗口里头发现pThis和this的值并不相等,pThis毫无疑问是预期的,this却总是多了一些字节,像是某个偏移量。于是联想到vc编译器虚函数相关实现机制和asm汇编出来的代码,总算搞清楚这个问题了。下面以一个简单的实例来说明:

   假设有如下类定义:

  class A
{
int xxx;
public:
virtual void func1(){}
};
class B
{
int xxx;
public:
virtual void *func2(){return NULL;}

};
class C:public A,public B
{
int ddd;
public:
virtual void *func2(){
return this;
}
virtual void func3(){}
};

  作如下调用:

  C t1;

 t1.func2();

 跟踪func2的执行过程,会发现,因为func2是一个虚调用,并且是从B继承下来的,所以在执行func2调用的时候this' 指针指向的是父类的地址.这通过汇编代码可以很容易发现


005C2930  lea         ecx,[ebp-28h] 
005C2933  call        C::C (4BAA2Fh) 

这两行对应构造t1的函数调用.请注意,ecx值即this指针为ebp-28

005C299A  lea         ecx,[ebp-20h] 
005C299D  call        C::func2 (4B7843h) 

这两行对应t1.func2()的调用.ecx值b变成了ebp-20 this'=this+8

所以进入C类的func2函数调用的时候,this的值实际上是父类B的首地址,会加个偏移量8,记为this'. 所以你用鼠标悬停查看this或者在监视窗口中看this总是这个值this'。但是根据this的c++标准定义,它应该等于调用时成员函数所在类的首地址.所以你在C的func2调用中存取this的值的时候它又会换算成正确的值this=this'-8.这在汇编代码中可以得到确认:

004C2CEC  mov         eax,dword ptr [this] 
004C2CEF  sub         eax,8 

最后返回的值eax=this'-8

总结如下:

1. 在c++代码中存取this的值得时候它总是等于本类的实例首地址

2.实际发生虚调用的时候要传this的值(thiscall通过ecx传递),它等于父类的实例地址。单继承的情况下两者一致,多继承的时候就可能差一个偏移量了.

所以在虚函数调用中看到this关键字,在汇编层面它并不一定等于自己的首地址哦.但是在语言层面是相等的.

(这在委托调用的时候传递this指针的值时需要注意,否则会出错)


最后再提一点,对于上面的虚函数调用

C t1;

t1.func2()

实际的汇编代码并不是一个虚调用,因为是通过类成员方法调用的,已经知道调用地址在哪里了.vc的编译器做了这个优化.

但对于指针的虚函数调用一般都是确实的虚调用.例如:

B *  p1=&t1;

C * p2=&t1;

p1->func2();

p2->func2();

都会生成如下的汇编代码:

005C2966  mov         ecx,dword ptr [ebp-34h] 
005C2969  add         ecx,8                                       //调整this指针
005C296C  mov         eax,dword ptr [ebp-34h] 
005C296F  mov         edx,dword ptr [eax+8]       //查找虚表调用地址
005C2972  mov         esi,esp                             //保存esp,因为是debug版会检测esp值
005C2974  mov         eax,dword ptr [edx] 
005C2976  call        eax  




你可能感兴趣的:(c/c++语言)