您的位置:首页 > 编程语言 > C语言/C++

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

2009-09-11 16:37 375 查看
快乐虾 http://blog.csdn.net/lights_joy/ lights@hb165.com


本文适用于
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 参考资料

vs2008下C++对象内存布局(5):vtbl(2009-9-11)
vs2008下C++对象内存布局(4):多重继承(2009-9-11)
vs2008下C++对象内存布局(3):加上虚函数(2009-9-10)
vs2008下C++对象内存布局(2):简单继承(2009-9-9)
vs2008下C++对象内存布局(1)(2009-9-9)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: