您现在的位置是:首页软件开发论文

软件开发论文继承情况下直接调用类成员函数地址

发布时间:2013-10-23 13:53:49更新时间:2013-10-23 13:55:14 1

  [摘要]分析在继承情况下如何取类的成员函数的地址以及调用该地址。

  [关键词]C++成员函数,this指针

  根据类成员函数的种类不同,在继承下如何取成员函数的地址以及调用该地址的情况也是有所区别的。另外还要注意的是多继承下情况又是如何。类的成员函数和标准的C函数不同,类的成员函数有一个隐藏的指针参数this,它指向一个类的实例。在VC++中,this一般通过ECX寄存器来传递,而普通的成员函数的参数被直接压在堆栈中。this作为参数和其他普通的参数有着本质的不同,即使一个成员函数被一个普通函数的调用,在标准C++中这个成员函数和其他的普通函数的情况不相同,因为没有thiscall这样的关键字来保证它像普通参数一样正常的调用。为此,我分别就以下三种情况作了深入的分析。

  一、最简单的单继承,非虚拟函数的情况

  classA{

  public:

  intAf(){return1;}

  };

  classB:publicA

  {

  public:

  intBf(){return2;}

  };

  假如我们建立了B类的一个成员函数指针。在这个例子中,Af和Bf都是B的成员函数,所以我们的成员函数指针可以指向Af或者Bf。但是Af需要一个this指针指向B::A(后面我叫它Athis),而Bf需要一个this指针指向B(后面我叫它Bthis)。编译器保证了A类在物理上保存在B类的头部(即B类的起始地址也就是一个A类的一个实例的起始地址),这意味着Athis==Bthis。

  二、继承情况下的虚拟函数

  classA{

  Public:

  virtualintfv(){return11;

  }

  };classB:publicA{

  Public:

  virtualintfv(){return22;}

  };

  现在A和B都定义了虚函数fv,按C++语法,如果通过指针调用fv,应该发生多态行为。利用下面的代码:

  DWORDA_fv,B_fv;

  GetMemberFuncAddr_VC6(A_fv,&A::fv);

  GetMemberFuncAddr_VC6(B_fv,&B::fv);

  Ax;By;

  CallMemberFunc(0,A_fv,&x,0);//A::fv

  CallMemberFunc(0,B_fv,&x,0);//B::fv

  CallMemberFunc(0,A_fv,&y,0);//A::fv

  CallMemberFunc(0,B_fv,&y,0);//B::fv

  输出如下:

  11

  11

  22

  22

  请注意第二行输出,B_fv取的是&B::fv,但由于传递的this指针产生是&x,所以实际上调用了A::fv。同样,第三行输出,取的是基类的函数地址,但由于实际对象是派生类,最后调用了派生类的函数。这说明取得的成员函数地址在虚拟函数的情况下仍然保持了正确的行为。源代码:GetMemberFuncAddr_VC6(B_fv,&B::fv);产生的汇编代码如下:pushofset@ILT+90(`vcall')(0040105f)。

  原来取B::fv地址的时候,并不是真的就将B::fv的地址传给了函数,而是传了一个vcall函数的地址。顾名思义,vcall当然是虚拟调用的意思。我们找到地址0040105f,@ILT+90(??_9@$BA@AE):0040105Fjmp`vcall'(00401380)。该地址只是ILT的一个项,直接跳转到真正的vcall函数(00401380)。找到00401380,就可以看到vcall的代码'vcall':

  00401380moveax,dwordptr[ecx];//将this指针视为dword类型,并将指向的内容(对象的首个//dword)放入eax.

  00401382jmpdwordptr[eax];//跳转到eax所指向的地址。

  代码执行的时候,ecx就是this指针,具体说就是上面对象x或y的地址。而eax就是对象x或y的第一个dword的值。对于有虚拟函数的类对象,其对象的首地址处总是一个指针,该指针指向一个虚函数的地址表。上面的对象由于只有一个虚函数,所以虚函数表也只有一项。因此,直接跳转到eax指向的地址就好。如果有多个虚函数,则eax还要加上一个偏移量,以定位到不同的虚函数。比如,如果有两个虚函数,则会有两个vcall代码,分别对应不同的虚函数,编译器根据取的是哪个虚函数的地址,则相应的用对应的vcall地址代替。

  三、多继承情况

  很明显,现在情况要复杂得多。首先,指定成员函数的时候可能会碰到冲突。其次,给定this指针的时候需要经过调整。

  classA{public:

  intAf(){return1;};};

  classB{public:

  intBf(){return2;};};

  classD:publicA,publicB{public:

  intDf(){return4;};};

  现在我们建立一个D类的成员函数指针。在这种情况下,我们的成员函数指针可以指向Af、Bf或Df。但是Af需要一个this指针指向D::A,而Bf需要一个this指针指向D::B。这时编译器不可能把A类和B类都放在D类的头部。所以,D类的一个成员函数指针不仅要说明要指明调用的是哪一个函数,还要指明使用哪一个this指针。编译器知道A类占用的空间有多大,所以它可以对Athis增加一个delta=sizeof(A)偏移量就可以将Athis指针转换为Bthis指针。

  综上所述,为了支持一般形式的成员函数指针,需要至少三条信息:函数的地址,需要增加到this指针上的delta位移量,和一个虚拟函数表中的索引。对于VC6.0来说,还需要第四条信息:虚拟函数表(vtable)的地址。另外,对虚拟继承可能还要特别处理,而在多继承的情况下,很多时候成员函数指针已经变成了一个结构体,这时要正确调用该指针就变得格外困难。当然,解决所有这些问题已经超出了这篇文章的范围。

  参考文献:

  [1]"MemberFunctionPointersandtheFastestPossibleC++Delegates",DonClugston

  [2]《直接调用类成员函数地址》,南风.


转载请注明来自:http://www.yueqikan.com/ruanjiankaifalw/22499.html