论继承情况下直接调用类成员函数地址.docx
文本预览下载声明
论继承情况下直接调用类成员函数地址 [论文关键词]C++成员函数 this指针 [论文摘要]分析在继承情况下如何取类的成员函数的地址以及调用该地址。 根据类成员函数的种类不同,在继承下如何取成员函数的地址以及调用该地址的情况也是有所区别的。另外还要注意的是多继承下情况又是如何。类的成员函数和标准的C函数不同,类的成员函数有一个隐藏的指针参数this,它指向一个类的实例。在VC++中,this一般通过ECX寄存器来传递,而普通的成员函数的参数被直接压在堆栈中。this作为参数和其他普通的参数有着本质的不同,即使一个成员函数被一个普通函数的调用,在标准C++中这个成员函数和其他的普通函数的情况不相同,因为没有thiscall这样的关键字来保证它像普通参数一样正常的调用。为此,我分别就以下三种情况作了深入的分析。 一、最简单的单继承,非虚拟函数的情况 class A { public: int Af(){ return 1; } }; class B : public A { public: int Bf(){return 2; } }; 假如我们建立了B类的一个成员函数指针。在这个例子中,Af和Bf都是B的成员函数,所以我们的成员函数指针可以指向Af或者Bf。但是Af需要一个this指针指向B::A(后面我叫它Athis),而Bf需要一个this指针指向B(后面我叫它Bthis)。编译器保证了A类在物理上保存在B类的头部(即B类的起始地址也就是一个A类的一个实例的起始地址),这意味着Athis == Bthis。 二、继承情况下的虚拟函数 class A{ Public: virtual int fv(){return 11; } }; class B : public A { Public: virtual int fv(){return 22; } }; 现在A和B都定义了虚函数fv,按C++语法,如果通过指针调用fv,应该发生多态行为。利用下面的代码: DWORD A_fv,B_fv; GetMemberFuncAddr_VC6(A_fv,amp;A::fv); GetMemberFuncAddr_VC6(B_fv,amp;B::fv); A x;B y; CallMemberFunc(0,A_fv,amp;x,0); // A::fv CallMemberFunc(0,B_fv,amp;x,0); // B::fv CallMemberFunc(0,A_fv,amp;y,0); // A::fv CallMemberFunc(0,B_fv,amp;y,0); // B::fv 输出如下: 11 11 22 22 请注意第二行输出,B_fv取的是amp;B::fv,但由于传递的this指针产生是amp;x,所以实际上调用了A::fv。同样,第三行输出,取的是基类的函数地址,但由于实际对象是派生类,最后调用了派生类的函数。这说明取得的成员函数地址在虚拟函数的情况下仍然保持了正确的行为。源代码: GetMemberFuncAddr_VC6(B_fv,amp;B::fv); 产生的汇编代码如下:push ofset @ILT+90(`vcall) (0040105f)。 原来取B::fv地址的时候,并不是真的就将B::fv的地址传给了函数,而是传了一个vcall函数的地址。顾名思义,vcall当然是虚拟调用的意思。我们找到地址0040105f,@ILT+90(??_9@$BA@AE):0040105F jmp `vcall 。该地址只是ILT的一个项,直接跳转到真正的vcall函数。找就可以看到vcall的代码vcall: mov eax,dword ptr [ecx] ;//将this指针视为dword类型,并将指向的内容(对象的首个//dword)放入eax. jmp dword ptr [eax] ;//跳转到eax所指向的地址。 代码执行的时候,ecx就是this指针,具体说就是上面对象x或y的地址。而eax就是对象x或y的第一个dword的值。对于有虚拟函数的类对象,其对象的首地址处总是一个指针,该指针指向一个虚函数的地址表。上面的对象由于只有一个虚函数,所以虚函数表也只有一项。因此,直接跳转到eax指向的地址就好。如果有多个虚函数,则eax还要加上一个偏移量,以定位到不同的虚函数。比如,如果有两个虚函数,则会有两个vcall代码,分别对应不同的虚函数,编译器根据取的是哪个虚函数的地址,则相应的用对应的vcall地
显示全部