本文主要针对成员函数指针,描述了相关成员函数指针的一些性质。
问题的引入
前些时候,对于一个功能涉及到了成员函数的使用,具体过程是,在一个模板里面,有一个内嵌Wrapper结构,这个Wrapper包含一个对象指针和一个成员函数指针,大致代码如下:
- // Template .h file
- template <typename T>
- class TClass
- {
- ...
- struct Wrapper
- {
- typedef ret(T::* Handler)(args);
- T* pSubject;
- Handler pHandler;
- };
- ...
- void SomeFunc(T* pS, Wrapper::Handler pH)
- {
- m_wrapper.pSubject = pS;
- m_wrapper.pHandler = pH;// Where problem occurs
- }
- ...
- Wrapper m_wrapper;
- };
而在使用上面类的时候,大致如下
- // In some .h file
- class SubjectClass; // In some .cpp file who include this .h file, compiler still can't know the definition of subject class.
- typedef TClass<SubjectClass> SubjectTClass;
-
- ...
当一切都写好了,运行程序的时候,悲剧来了,
m_wrapper.pHandler = pH; pH是一个有效的成员函数指针,但是m_wrapper.pHandler就是赋不上值。
好,转到反汇编看一看
注:下面的汇编码仅仅是为模拟问题,在VS 2008上生成
C++ Code :
- class A;
- typedef void(A::* AMemFunc)();
- AMemFunc memFunc;
- memFunc = 0; // Here "memFunc = 0" simulates "m_wrapper.pHandler = pH"
Assemble Code :
- 012613BE mov dword ptr [ebp-0ECh],0
- 012613C8 mov dword ptr [ebp-0E8h],0
- 012613D2 mov dword ptr [ebp-0E4h],0
- 012613DC mov dword ptr [ebp-0E0h],0FFFFFFFFh
- 012613E6 mov eax,dword ptr [ebp-0ECh]
- 012613EC mov dword ptr [memFunc],eax // First byte of memFunc
- 012613EF mov ecx,dword ptr [ebp-0E8h]
- 012613F5 mov dword ptr [ebp-10h],ecx // Second byte of memFunc
- 012613F8 mov edx,dword ptr [ebp-0E4h]
- 012613FE mov dword ptr [ebp-0Ch],edx // Third byte of memFunc
- 01261401 mov eax,dword ptr [ebp-0E0h]
- 01261407 mov dword ptr [ebp-8],eax // Forth byte of memFunc
m_wrapper.pHandler = pH 这一行代码生成的汇编码就如上面1~12的汇编码一样,这是啥玩意???
究其原因就在于在一个.cpp编译单元里面,对于类的成员函数指针,如果编译器看不到这个类的定义,那么编译器生成的成员函数指针的代码就如上面,no warning,no error。
成员函数指针解析
对于成员函数指针,不要期望他的size和普通函数指针的size是一致的,这区别于这个成员函数指针所属类是怎样的类。
1:单继承情况
对于单继承,(如果)所有的成员函数期望的this指针都是一致的,没有对this指针施加offset,(那么)这个时候这种类的成员函数指针和普通的函数指针大小是一样的,4 bytes。
low ---
4bytes funcPtr
---
这种情况下,成员函数的调用汇编码为:
lea ecx, [obj] // this pointer
call dword ptr[memFunc] // invoke member function
注意:对于一般的单继承情况,父类和子类的this指针是一样的,但是对于这种情况
class A{...};// A doesn't have virtual function
class B : public A{...}; // B has virtual function
A和B的this指针是不一样的,A的成员函数期望的this = B的成员函数中期望的this+4bytes,因为B到A要跳过vfptr指针。
所以这种情况涉及到了this指针的偏移,因此B的成员函数指针为8bytes。4bytes(FuncPtr)+4bytes(Offset)
这种情况和2情况是类似的
2:多继承情况
对于多继承,对于不同基类的成员函数(多态/非多态成员函数),他们期望的this指针和子类本身的this指针一般是需要一个offset的。那么这个时候,对于多继承类的成员函数指针不仅要记录实际成员函数的地址,还要记录一个对于该类this的一个offset。则这个时候为8 bytes。
low ---
4bytes funcPtr
4bytes offset
---
这个时候成员函数的调用汇编码为:
lea ecx, [obj] // this of obj
add ecx, dword ptr[memFunc+4] // this += offset
call dword ptr[memFunc] // invoke member function
3:虚继承类情况
对于虚继承,他涉及到一个vbtable(virtual base table),对于虚继承的类,他的内存布局在本类的内存布局上需要到vbtable上去找到相对于vbptr(virtual base table pointer)所在地址的偏移,才能得到这个虚继承类的地址。
对于虚继承的内存模型例子
所以这个时候的成员函数指针大小为12bytes,他的内存模型为
low ----
4bytes funcPtr
4bytes offset
4bytes offset in vbtable
----
而成员函数调用时候的伪码为:
vbtbl_off = offset in vbtable // ’offset in vbtable‘ is the highest 4bytes in memFunc
vb_this = addr_vbptr+*(dword*)(vbtable+vbtbl_off) // ‘this’ for virtual base class
this = vb_this + offset
invoke member function
对于这三种情况,这里只是大致的一个说明,更细致的情况,可写一个小程序看看他的反汇编码。
总结:
因为编译器对于成员函数指针的处理,在编译器处理非定义类的时候,具有这样的特殊的行为,而这种行为又不易察觉,所以个人觉得在使用成员函数的时候,自己有一个约定,约定总在定义类之后才使用该类的成员函数指针,而不要让编译器去决定。
对于C++在Multiple Inheritance和Virtual Inheritance的一些技术细节可参阅
Bjarne Stroustrup, "Multiple Inheritance for C++" ,
Jan Gray, "C++: Under the Hood"
阅读(793) | 评论(0) | 转发(0) |