vs2008下C++对象内存布局(6):指向成员函数的指针

快乐虾

http://blog.csdn.net/lights_joy/

[email protected]

 

本文适用于

Xp sp3

Vs2008

 

欢迎转载,但请保留作者信息

 

Vs支持指向成员函数的指针,沿用上文中的类进行试验:

class CParentA

{

public:

     int parenta_a;

     int parenta_b;

 

public: 

     virtual void parenta_f1() {this->parenta_a = 0x10;}

     virtual void parenta_f2() {this->parenta_a = 0x20;}

 

public: 

     void parenta_f3() {this->parenta_a = 0x30;}

     void parenta_f4() {this->parenta_a = 0x40;}

};

 

class CParentB

{

public:

     int parentb_a;

     int parentb_b;

 

public: 

     virtual void parentb_f1() {this->parentb_a = 0x50;}

     virtual void parentb_f2() {this->parentb_a = 0x60;}

 

public: 

     void parentb_f3() {this->parentb_a = 0x70;}

     void parentb_f4() {this->parentb_a = 0x80;}

};

 

class CChild : public CParentA, public CParentB

{

public:

     int child_a;

     int child_b;

 

public:       // 子类的函数

     virtual void child_f1() {this->child_a = 0x90;}

     virtual void child_f2() {this->child_a = 0xa0;}

 

public:  // 不可重载的函数

     void child_f3() {this->child_a = 0xb0;}

     void child_f4() {this->child_a = 0xc0;}

 

public:       // 重载父类A的函数

     virtual void parenta_f2() {this->child_a = 0xd0;}

     virtual void parenta_f1() {this->child_a = 0xe0;}

 

public:       // 重载父类B的函数

     virtual void parentb_f2() {this->child_a = 0xf0;}

     virtual void parentb_f1() {this->child_a = 0xff;}

};

 

 

CChild child, *pchild;

CParentA parent, *pparent;

 

1.1.1   指向非虚函数的指针

我们先用一个指针指向CParentA::parenta_f3这个非虚函数:

     void (__thiscall CParentA::* pp1)(void) = &CParentA::parenta_f3;

00411516 C7 45 F8 F5 10 41 00 mov         dword ptr [pp1],offset CParentA::parenta_f3 (4110F5h)

可以看到,它直接取的是函数的地址。

那么要如何调用这个函数呢?因为类成员函数的调用是需要传递一个this指针的,且这个指针应当指向一个CParentA的对象。我们直接把parent传递给它:

     (parent.* pp1)();

004114B4 8B F4            mov         esi,esp

004114B6 B9 70 81 41 00   mov         ecx,offset parent (418170h)

004114BB FF 55 F8         call        dword ptr [pp1]

004114BE 3B F4            cmp         esi,esp

004114C0 E8 CB FC FF FF   call        @ILT+395(__RTC_CheckEsp) (411190h)

经过这样的函数调用,与parent.parenta_a如愿改成了0x30

因为子类的内存布局的前一部分与父类一致,因此将子类指针传递进去应该也没什么问题,试试:

     (child.* pp1)();

004114B4 8B F4            mov         esi,esp

004114B6 B9 50 81 41 00   mov         ecx,offset child (418150h)

004114BB FF 55 F8         call        dword ptr [pp1]

004114BE 3B F4            cmp         esi,esp

004114C0 E8 CB FC FF FF   call        @ILT+395(__RTC_CheckEsp) (411190h)

没什么问题,同样将child.parenta_a改成了0x30

但如果反过来,试图进行下面的操作:

     void (__thiscall CChild::* pp1)(void) = &CChild::parenta_f3;

     (parent.* pp1)();

编译器将毫不客气地给出错误信息:

f:/projects/test/cpptest/cpptest.cpp(77) : error C2440: newline: 无法从“CParentA *”转换为“CChild *

        从基类型到派生类型的强制转换需要dynamic_cast static_cast

f:/projects/test/cpptest/cpptest.cpp(77) : error C2647: .*: 无法取消引用“void (__thiscall CChild::* )(void)(在“CParentA”上)

1.1.2   指向虚函数的指针

接着看看当指针指向虚函数时又会如何:

     void (__thiscall CParentA::* pp2)(void) = &CParentA::parenta_f1;

004114AD C7 45 EC 30 12 41 00 mov         dword ptr [pp2],offset CParentA::`vcall'{0}' (411230h)

 

     (parent.* pp2)();

004114B4 8B F4            mov         esi,esp

004114B6 B9 70 81 41 00   mov         ecx,offset parent (418170h)

004114BB FF 55 EC         call        dword ptr [pp2]

004114BE 3B F4            cmp         esi,esp

004114C0 E8 CB FC FF FF   call        @ILT+395(__RTC_CheckEsp) (411190h)

这个时候,pp2指针指向了一个叫CParentA::`vcall'{0}'的地方,看看它的代码:

00411230 E9 0B 06 00 00   jmp         CParentA::`vcall'{0}' (411840h)

…………

CParentA::`vcall'{0}':

00411840 8B 01            mov         eax,dword ptr [ecx]

00411842 FF 20            jmp         dword ptr [eax]

还是取VTBL再加上偏移量进行计算后跳转。

当我们用子类对象的指针做为参数调用这个函数指针时,又会发生什么呢?

     (child.* pp2)();

004114B4 8B F4            mov         esi,esp

004114B6 B9 50 81 41 00   mov         ecx,offset child (418150h)

004114BB FF 55 EC         call        dword ptr [pp2]

004114BE 3B F4            cmp         esi,esp

004114C0 E8 CB FC FF FF   call        @ILT+395(__RTC_CheckEsp) (411190h)

没什么两样,只是由于这个时候传递的是子类的VTBL,因此它将调用子类的虚函数。

1.1.3   指向父类B成员函数的指针

我们现在知道,当我们调用父类B的虚函数时,传递的this指针并不是指向子类对象的首地址,那么,当我们定义一个指向父类B的虚函数的指针时,它是否也会自动计算this指针的值呢?试试:

     void (__thiscall CParentB::* pp2)(void) = &CParentB::parentb_f1;

004114AD C7 45 EC 35 12 41 00 mov         dword ptr [pp2],offset CParentA::`vcall'{4}' (411235h)

 

     (child.* pp2)();

004114B4 B8 50 81 41 00   mov         eax,offset child (418150h)

004114B9 85 C0            test        eax,eax

004114BB 74 10            je          wmain+6Dh (4114CDh)

004114BD B9 50 81 41 00   mov         ecx,offset child (418150h)

004114C2 83 C1 0C         add         ecx,0Ch

004114C5 89 8D 24 FF FF FF mov         dword ptr [ebp-0DCh],ecx

004114CB EB 0A            jmp         wmain+77h (4114D7h)

004114CD C7 85 24 FF FF FF 00 00 00 00 mov         dword ptr [ebp-0DCh],0

004114D7 8B F4            mov         esi,esp

004114D9 8B 8D 24 FF FF FF mov         ecx,dword ptr [ebp-0DCh]

004114DF FF 55 EC         call        dword ptr [pp2]

004114E2 3B F4            cmp         esi,esp

004114E4 E8 A7 FC FF FF   call        @ILT+395(__RTC_CheckEsp) (411190h)

的确,vs自动计算了this指针的值,将它加上了父类A的大小12个字节(0Ch),然后再进行函数调用!

 

 

 

 

 

 

2       参考资料

vs2008C++对象内存布局(5):vtbl(2009-9-11)

vs2008C++对象内存布局(4):多重继承(2009-9-11)

vs2008C++对象内存布局(3):加上虚函数(2009-9-10)

vs2008C++对象内存布局(2):简单继承(2009-9-9)

vs2008C++对象内存布局(1(2009-9-9)

 

 

 

 

 

你可能感兴趣的:(vs2008下C++对象内存布局(6):指向成员函数的指针)