前言:在CSDN论坛经常会看到一些关于类成员函数指针的问题,起初我并不在意,以为成员函数指针和普通的函数指针是一样的,没有什么太多需要讨论的。当 我找来相关书籍查阅了一番以后,突然意识到我以前对成员函数指针的理解太过于幼稚和肤浅了,它即不像我以前认为的那样简单,它也不像我以前认为的那样"默 默无闻"。强烈的求知欲促使我对成员函数进行进一步的学习并有了这篇文章。
一。理论篇
在进行深入学习和分析之前,还是先看看书中是怎么介绍成员函数的。总结一下类成员函数指针的内容,应该包含以下几个知识点:
1。成员函数指针并不是普通的函数指针。
2。编译器提供了几个新的操作符来支持成员函数指针操作:
1)操作符"::*"用来声明一个类成员函数指针,例如:
typedefvoid(Base::*PVVBASEMEMFUNC)(void);//Baseisaclass
2)操作符"->*"用来通过对象指针调用类成员函数指针,例如:
//pBaseisaBasepointerandwellinitialized
//pVIBaseMemFuncisamemberfunctionpointerandwellinitialized
(pBase->*pVIBaseMemFunc)();
3)操作符".*"用来通过对象调用类成员函数指针,例如:
//baseObjisaBaseobject
//pVIBaseMemFuncisamemberfunctionpointerandwellinitialized
(baseObj.*pVIBaseMemFunc)();
3。成员函数指针是强类型的。
typedefvoid(Base::*PVVBASEMEMFUNC)(void);
typedefvoid(Derived::*PVVDERIVEMEMFUNC)(void);
PVVBASEMEMFUNC和PVVDERIVEMEMFUNC是两个不同类型的成员函数指针类型。
4。由于成员函数指针并不是真真意义上的指针,所以成员函数指针的转化就受限制。具体的转化细节依赖于不同的编译器,甚至是同一个编译器的不同版本。不过,处于同一个继承链中的不同类之间override的不同函数和虚函数还是可以转化的。
void*pVoid=reinterpret_cast<void*>(pVIBaseMemFunc); //error
int*pInt=reinterpret_cast<int*>(pVIBaseMemFunc); //error
pVIDeriveMemFunc=static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc); //OK
二。实践篇
有了上面的理论知识,我们对类成员函数指针有了大概的了解,但是我们对成员函数指针还存在太多的疑惑。既然说成员函数指针不是指针,那它到底是什么东东? 编译器为什么要限制成员函数指针转化?老办法,我们还是分析汇编代码揭示其中的秘密。首先,我写了这样两个具有继承关系的类:
classBase {
public:
//ordinarymemberfunction
voidsetValue(intiValue);
//virtualmemberfunction
virtualvoiddumpMe();
virtualvoidfoobar();
protected:
intm_iValue;
};
classDerived:publicBase{
public:
//ordinarymemberfunction
voidsetValue(intiValue);
//virtualmemberfunction
virtualvoiddumpMe();
virtualvoidfoobar();
private:
doublem_fValue;
};
接着,我又定义了一些成员函数指针类型:
typedefvoid(Base::*PVVBASEMEMFUNC)(void);
typedefvoid(Derived::*PVVDERIVEMEMFUNC)(void);
typedefvoid(Base::*PVIBASEMEMFUNC)(int);
typedefvoid(Derived::*PVIDERIVEMEMFUNC)(int);
最后,在main函数写了一些测试代码:
int_tmain(intargc,_TCHAR*argv[])
{
PVIBASEMEMFUNCpVIBaseMemFunc=&Base::setValue;
PVIDERIVEMEMFUNCpVIDeriveMemFunc=static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc);
PVVBASEMEMFUNCpVVBaseMemFunc=&Base::foobar;
PVVDERIVEMEMFUNCpVVDeriveMemFunc=static_cast<PVVDERIVEMEMFUNC>(pVVBaseMemFunc);
BasebaseObj;
(baseObj.*pVIBaseMemFunc)(10);
(baseObj.*pVVBaseMemFunc)();
DerivedderiveObj;
(deriveObj.*pVIDeriveMemFunc)(20);
(deriveObj.*pVVDeriveMemFunc)();
return0;
}
成功编译后生成汇编代码。老规矩,在分析汇编代码的过程中还是只分析对解决问题有意义的汇编代码,其他的就暂时忽略。
1。成员函数指针不是指针。从代码看出,在main函数的调用栈(calling stack)中首先依次压入四个成员函数指针,如果它们是普通指针的话,它们之间的偏移量应该是4个字节,可是实际的情况却是这样的:
_deriveObj$=-88
_baseObj$=-60
_pVVDeriveMemFunc$=-44
_pVVBaseMemFunc$=-32
_pVIDeriveMemFunc$=-20
_pVIBaseMemFunc$=-8
_argc$=8
_argv$=12
由此可以看出,他们之间的偏移量是12个字节。这12个字节中应该可以包含三个指针,其中的一个指针应该指向函数的地址,那另外两个指针又指向那里呢?在《C++ Common Knowledge: Essential Intermediate Programming》(中文译名:C++必知必会)这本书的第16章对这部分的内容做了说明,这个12个字节的偏移量正好印证了书中的内容:
”The implementation of the pointer to member function must store within itself information as to whether the member function to which it refers is virtual or nonvirtual, information about where to find the appropriate virtual function table pointer (see The Compiler Puts Stuff in Classes [11, 37]), an offset to be added to or subtracted from the function's this pointer (see Meaning of Pointer Comparison [28, 97]), and possibly other information. A pointer to member function is commonly implemented as a small structure that contains this information, although many other implementations are also in use. Dereferencing and calling a pointer to member function usually involves examining the stored information and conditionally executing the appropriate virtual or nonvirtual function calling sequence.“
2。成员函数指针的转化。本文所采用的代码是想比较普通成员函数指针和虚函数指针在转化的过程中存在那些差异:
;PVIBASEMEMFUNCpVIBaseMemFunc=&Base::setValue;
movDWORDPTR_pVIBaseMemFunc$[ebp],OFFSETFLAT:?setValue@Base@@QAEXH@Z;
取出Base::setValue函数的地址,存放于变量pVIBaseMemFunc所占内存的前4个字节(DWORD)中。
;PVVBASEMEMFUNCpVVBaseMemFunc=&Base::foobar;
movDWORDPTR_pVVBaseMemFunc$[ebp],OFFSETFLAT:??_9@$B3AE;`vcall'
取出符号”??_9@$B3AE“的值,存放于变量pVVBaseMemFunc所占内存的前4个字节(DWORD)中。
对于符号”??_9@$B3AE“,我又找到了这样的汇编代码:
_TEXTSEGMENT
??_9@$B3AEPROCNEAR;`vcall',COMDAT
moveax,DWORDPTR[ecx]
jmpDWORDPTR[eax+4]
??_9@$B3AEENDP;`vcall'
_TEXTENDS
符号”??_9@$B3AE“代表的应该是一个存根函数,这个函数首先根据this指针获得虚函数表的指针,然后将指令再跳转到相应的虚函数的地址。
由此可以看出,对于虚函数,即使是用过成员函数指针间接调用,仍然具有和直接调用一样的特性。
;PVIDERIVEMEMFUNCpVIDeriveMemFunc=static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc);
moveax,DWORDPTR_pVIBaseMemFunc$[ebp]
movDWORDPTR_pVIDeriveMemFunc$[ebp],eax
直接将变量pVIBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量_pVIDeriveMemFunc所占内存的前4个字节中。
;PVVDERIVEMEMFUNCpVVDeriveMemFunc=static_cast<PVVDERIVEMEMFUNC>(pVVBaseMemFunc);
moveax,DWORDPTR_pVVBaseMemFunc$[ebp]
movDWORDPTR_pVVDeriveMemFunc$[ebp],eax
直接将变量pVVBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量pVVDeriveMemFunc所占内存的前4个字节中。
由此可以看出,基类的成员函数指针转化到相应的派生类的成员函数指针,值保持不变。当然这里的例子继承关系相对来说比较简单,如果存在多继承和虚继承的情况下,结果可能会复杂的多。
3。函数调用
下面的函数调用都大同小异,这里是列出其中的一个:
;(baseObj.*pVIBaseMemFunc)(10);
movesi,esp
push10;0000000aH
leaecx,DWORDPTR_baseObj$[ebp]
callDWORDPTR_pVIBaseMemFunc$[ebp]
cmpesi,esp
call__RTC_CheckEsp
这里的汇编代码并没有给我们太多新鲜的内容:将对象的首地址(this指针)存放于寄存器ECX中,接着就将指令转到变量_pVIBaseMemFunc所占内存的前4个字节所表示的地址。
到了这里,我们应该对成员函数指针有了进一步的了解。
分享到:
相关推荐
C++Hook(钩子)编程,通过内联汇编,使类成员函数代替全局函数(静态函数)[收集].pdf
汇编 入口 函数汇编 入口 函数汇编 入口 函数汇编 入口 函数汇编 入口 函数汇编 入口 函数汇编 入口 函数汇编 入口 函数汇编 入口 函数汇编 入口 函数
(接中篇)委托(delegate)和成员函数指针不同,你不难发现委托的用处。最重要的,使用委托可以很容易地实现一个Subject/Observer设计模式的改进版[GoF,p.293]。Observer(观察者)模式显然在GUI中有很多的应用,但...
C++Hook(钩子)编程,通过内联汇编,使类成员函数代替全局函数(静态函数).pdf
易语言源码易语言汇编取指针模块源码.rar 易语言源码易语言汇编取指针模块源码.rar 易语言源码易语言汇编取指针模块源码.rar 易语言源码易语言汇编取指针模块源码.rar 易语言源码易语言汇编取指针模块源码.rar ...
CHook钩子编程通过内联汇编使类成员函数代替全局函数静态函数的知识点.pdf
易语言汇编取指针模块源码,汇编取指针模块,取文本指针,取小数指针,取整数指针,取子程序指针,取字节集指针,取字节指针,执行字节集,执行函数,调用函数,子程序1
易语言源码易语言汇编取类指针源码.rar 易语言源码易语言汇编取类指针源码.rar 易语言源码易语言汇编取类指针源码.rar 易语言源码易语言汇编取类指针源码.rar 易语言源码易语言汇编取类指针源码.rar 易语言源码...
易语言汇编指针到长整数源码,汇编指针到长整数,指针到长整数
在开发板上学习自编程序实验,是在学习过程中必须的一个过程,这个小程在2.8寸液晶屏上显示指针式时钟。
51修改PC指针的方式,汇编编写,就几行代码,复制到编译器环境下可直接运行,(我用的wave)来源于本人在开发中的一段异常代码,是不是有其他朋友也知道就不得而知了,资源分的话……囊中羞涩,见谅见谅。
当遇到较大较复杂的编程任务时,...2.《从汇编看C++ 之类成员》 3.《从汇编看C++ 之引用指针动态内存》 4.《从汇编看C++ 之虚函数接口多态》 欢迎大家下载,并到我微博上交流指正,我会继续努力(我微博上也叫小力庭)
VB调用函数指针,支持任意类型返回值(UserType必须传入指针),任意类型任意个数参数(不直接支持Optional,ParamArray),纯VB+内嵌汇编,汇编代码改自"阿国哥"的调用函数指针内嵌汇编代码,当然我不懂汇编,调试了很...
汇编 api windows 函数 中文 汇编 api windows 函数 中文 汇编 api windows 函数 中文
易语言汇编指针到字节集源码。@易语言在线学习。
易语言汇编指针到字节集源码.7z
易语言源码易语言汇编指针到文本源码.rar
C语言 指针
我们的类成员函数分为inline、virtual、static、normal。 **normal成员函数:**其地址和一般函数的地址没有区别,就是函数代码在内存中的真实地址,但其调用要绑定到一个实实在在的对象上。取其地址需要使用“&”...
并修改了许多bug 使用方法: 反汇编函数 ulong Disasm(char *src,ulong srcsize,ulong srcip, t_disasm *disasm,int disasmmode) 返回值是指令长度 src指向需要反汇编的机器码指针 srcsize机器码的总长度 srcip机器...