论继承情况下直接调用类成员函数地址(2)
作者:佚名; 更新时间:2014-12-05
11
11
22
22
请注意第二行输出,B_fv取的是&B::fv,但由于传递的this指针产生是&x,所以实际上调用了A::fv。同样,第三行输出,取的是基类的函数地址,但由于实际对象是派生类,最后调用了派生类的函数。这说明取得的成员函数地址在虚拟函数的情况下仍然保持了正确的行为。源代码: GetMemberFuncAddr_VC6(B_fv,&B::fv); 产生的汇编代码如下:push ofset @ILT+90(`vcall') (0040105f)。
原来取B::fv地址的时候,并不是真的就将B::fv的地址传给了函数,而是传了一个vcall函数的地址。顾名思义,vcall当然是虚拟调用的意思。我们找到地址0040105f,@ILT+90(??_9@$BA@AE):0040105F jmp `vcall' (00401380)。该地址只是ILT的一个项,直接跳转到真正的vcall函数(00401380)。找到00401380,就可以看到vcall的代码'vcall':
00401380 mov eax,dword ptr [ecx] ;//将this指针视为dword类型,并将指向的内容(对象的首个//dword)放入eax.
00401382 jmp dword ptr [eax] ;//跳转到eax所指向的地址。
代码执行的时候,ecx就是this指针,具体说就是上面对象x或y的地址。而eax就是对象x或y的第一个dword的值。对于有虚拟函数的类对象,其对象的首地址处总是一个指针,该指针指向一个虚函数的地址表。上面的对象由于只有一个虚函数,所以虚函数表也只有一项。因此,直接跳转到eax指向的地址就好。如果有多个虚函数,则eax还要加上一个偏移量,以定位到不同的虚函数。比如,如果有两个虚函数,则会有两个vcall代码,分别对应不同的虚函数,编译器根据取的是哪个虚函数的地址,则相应的用对应的vcall地址代替。
三、多继承情况
很明显,现在情况要复杂得多。首先,指定成员函数的时候可能会碰到冲突。其次,给定this指针的时候需要经过调整。
class A {public:
int Af() {return 1; }; };
class B {public:
int Bf() {return 2; };};
class D: public A, public B {public:
int Df() {return 4; }; };
现在我们建立一个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]"Member Function Pointers and the Fastest Possible C++ Delegates",Don Clugston
[2]《直接调用类成员函数地址》,南风.
下一篇:论计算机辅助教学与化学教学现代化
热门论文