`
weiyinchao88
  • 浏览: 1235186 次
文章分类
社区版块
存档分类
最新评论

透过汇编另眼看世界之类成员函数指针

 
阅读更多

前言:在CSDN论坛经常会看到一些关于类成员函数指针的问题,起初我并不在意,以为成员函数指针和普通的函数指针是一样的,没有什么太多需要讨论的。当 我找来相关书籍查阅了一番以后,突然意识到我以前对成员函数指针的理解太过于幼稚和肤浅了,它即不像我以前认为的那样简单,它也不像我以前认为的那样"默 默无闻"。强烈的求知欲促使我对成员函数进行进一步的学习并有了这篇文章。

一。理论篇
在进行深入学习和分析之前,还是先看看书中是怎么介绍成员函数的。总结一下类成员函数指针的内容,应该包含以下几个知识点:
1。成员函数指针并不是普通的函数指针。
2。编译器提供了几个新的操作符来支持成员函数指针操作:
1)操作符"::*"用来声明一个类成员函数指针,例如:
typedef
void(Base::*PVVBASEMEMFUNC)(void);//Baseisaclass
2)操作符"->*"用来通过对象指针调用类成员函数指针,例如:
//pBaseisaBasepointerandwellinitialized
//pVIBaseMemFuncisamemberfunctionpointerandwellinitialized

(pBase->*pVIBaseMemFunc)();

3)操作符".*"用来通过对象调用类成员函数指针,例如:
//baseObjisaBaseobject
//pVIBaseMemFuncisamemberfunctionpointerandwellinitialized

(baseObj.*pVIBaseMemFunc)();

3。成员函数指针是强类型的。
typedefvoid(Base::*PVVBASEMEMFUNC)(void);
typedef
void(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);
typedef
void(Derived::*PVVDERIVEMEMFUNC)(void);
typedef
void(Base::*PVIBASEMEMFUNC)(int);
typedef
void(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
push
10;0000000aH
leaecx,DWORDPTR_baseObj$[ebp]
callDWORDPTR_pVIBaseMemFunc$[ebp]
cmpesi,esp
call__RTC_CheckEsp
这里的汇编代码并没有给我们太多新鲜的内容:将对象的首地址(this指针)存放于寄存器ECX中,接着就将指令转到变量_pVIBaseMemFunc所占内存的前4个字节所表示的地址。

到了这里,我们应该对成员函数指针有了进一步的了解。
分享到:
评论

相关推荐

    通过函数指针调用C++非静态成员函数

    这段汇编代码的作用是将成员函数指针转换成普通函数指针,以便后续可以直接调用。 4. **调用成员函数**: ```cpp X x; __asm { push ecx lea ecx, x pfoo(3, 4); pop ecx } ``` 在这里,我们使用了汇编...

    对象的函数指针(c++)

    成员函数指针是C++中一个独特且强大的特性,它允许我们存储和传递类的成员函数,以便在后续的代码中动态调用。然而,与普通的函数指针不同,成员函数指针涉及到类的实例和作用域,这使得它们的使用和理解相对复杂。 ...

    C++Hook(钩子)编程,通过内联汇编,使类成员函数代替全局函数(静态函数)[收集].pdf

    C++ Hook(钩子)编程,通过内联汇编,使类成员函数代替全局函数(静态函数) 本文研究了C++ Hook(钩子)编程中,使类成员函数代替全局函数(静态函数)的技术。通过内联汇编,构造类对象独享的函数(委托),完成了类成员...

    CHook钩子编程通过内联汇编使类成员函数代替全局函数静态函数的知识点.pdf

    本文主要研究类成员函数与普通函数之区别,以及不同调用方式之间的区别,进而通过内联汇编语句模仿特定的调用,从而完成通过普通函数指针调用类成员函数的功能。我们将探讨C++ Hook钩子编程中类成员函数与全局函数的...

    C++Hook(钩子)编程,通过内联汇编,使类成员函数代替全局函数(静态函数).pdf

    通过内联汇编,我们可以构造一个委托(或称代理)函数,这个函数在内部调用类的成员函数,并正确处理`this`指针。具体做法是,我们可以在委托函数中使用汇编代码,手动将`this`指针压栈,然后跳转到成员函数的地址...

    Keil C51中函数指针的使用

    最后,带参数的函数指针一般来说,函数参数是通过堆栈来传递,用PUSH和POP汇编指令来实现的。但由于8051体系及其编译器的一些限制,使得其函数参数的传递需要一些特殊的方法。例如: ```c void (*pfun) (char, ...

    如何让类的成员函数作为回调函数

    相比之下,类的成员函数默认采用`__thiscall`调用约定。在这种约定中,第一个参数`this`指向该成员函数所属的对象,这个指针是通过ECX寄存器传递的,而不是通过堆栈。其余参数仍然按照从右向左的顺序压栈。 ``` __...

    用汇编的眼光看C/C++之深入指针

    ### 用汇编的眼光看C/C++之深入指针 #### 概述 在C/C++编程语言中,指针是一种极为重要的数据类型,能够直接操纵内存,从而提高程序的性能与灵活性。本文将深入探讨指针的概念及其在C/C++中的应用,并结合汇编语言...

    汇编入口点 汇编 入口 函数汇编 入口 函数

    1. **栈初始化**:设置栈指针寄存器(如x86架构下的ESP/RSP),为程序的局部变量和调用函数预留空间。 2. **数据段初始化**:如果程序有全局变量或静态变量,入口点可能包含初始化这些数据的指令。 3. **参数传递**...

    成员函数指针与高性能的C++委托(下篇)

    (接中篇)委托(delegate)和成员函数指针不同,你不难发现委托的用处。最重要的,使用委托可以很容易地实现一个Subject/Observer设计模式的改进版[GoF,p.293]。Observer(观察者)模式显然在GUI中有很多的应用,但...

    易语言汇编指针到文本源码

    标题中的“易语言汇编指针到文本源码”所指代的资源,很可能是一个程序实例,该实例不仅可以作为学习工具,供易语言学习者进行实践操作,更可以作为一个实用工具,帮助处理实际的编程任务。资源中的描述“@易语言...

    51修改PC指针汇编例程

    51修改PC指针的方式,汇编编写,就几行代码,复制到编译器环境下可直接运行,(我用的wave)来源于本人在开发中的一段异常代码,是不是有其他朋友也知道就不得而知了,资源分的话……囊中羞涩,见谅见谅。

    APIHook、InlineHook库,使用C++11编写,可将回调函数绑定到类成员函数

    在本库中,`corehook.hpp`可能包含了APIHook的核心实现,`opcode.hpp`可能涉及到了汇编级别的操作,而`corefunctor.hpp`可能是关于如何处理回调函数的封装,尤其是与类成员函数绑定的部分。 在`detail`目录下,通常...

    易语言汇编指针到字节集源码

    例如,可能有一个内置函数可以直接将指针数据转化为字节集,而不需要手动编写汇编代码。不过,理解汇编和指针的概念对于进行这种底层操作仍然是必要的。 在"SanYe"这个标签下,我们可以推测这是由知名开发者或团队...

    从汇编角度看虚函数和普通成员函数的调用过程

    相比之下,虚成员函数(virtual member function)引入了多态性,使得可以通过基类指针或引用调用派生类的重写版本。虚函数的地址并不直接指向函数体,而是指向虚函数表(vtable)中的一个条目。vtable是一个由...

    易语言汇编取指针模块

    然而,通过汇编取指针模块,可以直接通过内存地址调用函数,这对于处理C/C++等编写的动态链接库(DLL)或实现一些高级的程序设计模式如函数对象和闭包非常有用。 总结起来,易语言汇编取指针模块是一个强大的工具,...

    易语言源码易语言汇编取指针模块源码.rar

    易语言源码易语言汇编取指针模块源码.rar 易语言源码易语言汇编取指针模块源码.rar 易语言源码易语言汇编取指针模块源码.rar 易语言源码易语言汇编取指针模块源码.rar 易语言源码易语言汇编取指针模块源码.rar ...

    AVR-GCC如何调用存储于Flash中的指向函数的指针

    当涉及到函数指针时,AVR-GCC提供了将函数指针存储到Flash中的能力,并能够从Flash中正确调用这些函数指针。本文将详细介绍如何在AVR-GCC环境下使用函数指针指向Flash中的函数,并调用它们。 首先,需要了解的是,...

    易语言源码易语言汇编取类指针源码.rar

    易语言源码易语言汇编取类指针源码.rar 易语言源码易语言汇编取类指针源码.rar 易语言源码易语言汇编取类指针源码.rar 易语言源码易语言汇编取类指针源码.rar 易语言源码易语言汇编取类指针源码.rar 易语言源码...

    如何让API回调你的VC类成员函数而不是静态函数

    ### 如何让API回调你的VC类成员函数而不是静态函数 #### 概述 在软件开发过程中,特别是使用C++进行Windows编程时,我们经常会遇到需要将一个类的成员函数作为回调函数传递给某个API(应用程序接口)的情况。然而...

Global site tag (gtag.js) - Google Analytics