三九宝宝网宝宝百科宝宝知识

通过汇编代码理解成员函数指针并不是指针

11月22日 编辑 39baobao.com

[3月财务个人工作总结汇编]3月财务个人工作总结范文汇编一、坚持政治学习和业务学习制度,改进提高工作作风。xxxx年,我处建立了学习制度,每周三下午,我们都组织全处员工进行政治学习。通过认真学习马列...+阅读

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

一。理论篇

在进行深入学习和分析之前,还是先看看书中是怎么介绍成员函数的。总结一下类成员函数指针的内容,应该包含以下几个知识点:

1。成员函数指针并不是普通的函数指针。

2。编译器提供了几个新的操作符来支持成员函数指针操作:

1) 操作符"::*"用来声明一个类成员函数指针,例如:

typedef void (Base::*PVVBASEMEMFUNC)(void); Base is a class

2) 操作符"->*"用来通过对象指针调用类成员函数指针,例如:

pBase is a Base pointer and well initialized

pVIBaseMemFunc is a member function pointer and well initialized

(pBase->*pVIBaseMemFunc)();

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

baseObj is a Base object

pVIBaseMemFunc is a member function pointer and well initialized

(baseObj.*pVIBaseMemFunc)();

3。成员函数指针是强类型的。 typedef void (Base::*PVVBASEMEMFUNC)(void);

typedef void (Derived::*PVVDERIVEMEMFUNC)(void);

PVVBASEMEMFUNC和PVVDERIVEMEMFUNC是两个不同类型的成员函数指针类型。

4。由于成员函数指针并不是真真意义上的指针,所以成员函数指针的转化就受限制。具体的转化细节依赖于不同的编译器,甚至是同一个编译器的不同版本。不过,处于同一个继承链中的不同类之间override的不同函数和虚函数还是可以转化的。 void* pVoid = reinterpret_cast(pVIBaseMemFunc); error

int* pInt = reinterpret_cast(pVIBaseMemFunc); error

pVIDeriveMemFunc = static_cast(pVIBaseMemFunc); OK

二。实践篇

有了上面的理论知识,我们对类成员函数指针有了大概的了解,但是我们对成员函数指针还存在太多的疑惑。既然说成员函数指针不是指针,那它到底是什么东东? 编译器为什么要限制成员函数指针转化?老办法,我们还是分析汇编代码揭示其中的秘密。

首先,我写了这样两个具有继承关系的类: class Base {

public:

ordinary member function

void setValue(int iValue);

virtual member function

virtual void dumpMe();

virtual void foobar();

protected:

int m_iValue;

};

class Derived:public Base{

public:

ordinary member function

void setValue(int iValue);

virtual member function

virtual void dumpMe();

virtual void foobar();

private:

double m_fValue;

};

接着,我又定义了一些成员函数指针类型: typedef void (Base::*PVVBASEMEMFUNC)(void);

typedef void (Derived::*PVVDERIVEMEMFUNC)(void);

typedef void (Base::*PVIBASEMEMFUNC)(int);

typedef void (Derived::*PVIDERIVEMEMFUNC)(int);

最后,在main函数写了一些测试代码: int _tmain(int argc, _TCHAR* ar[])

{

PVIBASEMEMFUNC pVIBaseMemFunc =

PVIDERIVEMEMFUNC pVIDeriveMemFunc = static_cast(pVIBaseMemFunc);

PVVBASEMEMFUNC pVVBaseMemFunc =

PVVDERIVEMEMFUNC pVVDeriveMemFunc = static_cast(pVVBaseMemFunc);

Base baseObj;

(baseObj.*pVIBaseMemFunc)(10);

(baseObj.*pVVBaseMemFunc)();

Derived deriveObj;

(deriveObj.*pVIDeriveMemFunc)(20);

(deriveObj.*pVVDeriveMemFunc)();

return 0;

}成功编译后生成汇编代码。老规矩,在分析汇编代码的过程中还是只分析对解决问题有意义的汇编代码,其他的就暂时忽略。

1。成员函数指针不是指针。从代码看出,在main函数的调用栈(calling stack)中首先依次压入四个成员函数指针,如果它们是普通指针的话,它们之间的偏移量应该是4个字节,可是实际的情况却是这样的: _deriveObj$ = -88

_baseObj$ = -60

_pVVDeriveMemFunc$ = -44

_pVVBaseMemFunc$ = -32

_pVIDeriveMemFunc$ = -20

_pVIBaseMemFunc$ = -8

_argc$ = 8

_ar$ = 12

由此可以看出,他们之间的偏移量是12个字节。这12个字节中应该可以包含三个指针,其中的一个指针应该指向函数的地址,那另外两个指针又指向那里呢?在《

C++ mon 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 piler Puts Stuff in Classes [11, 37]), an offset to be added to or subtracted from the function’s this pointer (see Meaning of Pointer parison [28, 97]), and possibly other information. A pointer to member function is monly 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。成员函数指针的转化。本文所采用的代码是想比较普通成员函数指针和虚函数指针在转化的过程中存在那些差异: ; PVIBASEMEMFUNC pVIBaseMemFunc =

mov DWORD PTR _pVIBaseMemFunc$[ebp], OFFSET FLAT:?setValue#Base##QAEXH#Z ;

取出Base::setValue函数的地址,存放于变量pVIBaseMemFunc所占内存的前4个字节(DWORD)中。

; PVVBASEMEMFUNC pVVBaseMemFunc =

mov DWORD PTR _pVVBaseMemFunc$[ebp], OFFSET FLAT:??_9#$B3AE ; `vcall’

取出符号”??_9#$B3AE“的值,存放于变量pVVBaseMemFunc所占内存的前4个字节(DWORD)中。

对于符号”??_9#$B3AE“,我又找到了这样的汇编代码: _TEXT SEGMENT

??_9#$B3AE PROC NEAR ; `vcall’, DAT

mov eax, DWORD PTR [ecx]

jmp DWORD PTR [eax+4]

??_9#$B3AE ENDP ; `vcall’

_TEXT ENDS

符号”??_9#$B3AE“代表的应该是一个存根函数,这个函数首先根据this指针获得虚函数表的指针,然后将指令再跳转到相应的虚函数的地址。由此可以看出,对于虚函数,即使是用过成员函数指针间接调用,仍然具有和直接调用一样的特性。

; PVIDERIVEMEMFUNC pVIDeriveMemFunc = static_cast(pVIBaseMemFunc);

mov eax, DWORD PTR _pVIBaseMemFunc$[ebp]

mov DWORD PTR _pVIDeriveMemFunc$[ebp], eax

直接将变量pVIBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量_pVIDeriveMemFunc所占内存的前4个字节中。

; PVVDERIVEMEMFUNC pVVDeriveMemFunc = static_cast(pVVBaseMemFunc);

mov eax, DWORD PTR _pVVBaseMemFunc$[ebp]

mov DWORD PTR _pVVDeriveMemFunc$[ebp], eax

直接将变量pVVBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量pVVDeriveMemFunc所占内存的前4个字节中。由此可以看出,基类的成员函数指针转化到相应的派生类的成员函数指针,值保持不变。当然这里的例子继承关系相对来说比较简单,如果存在多继承和虚继承的情况下,结果可能会复杂的多。

3。函数调用

下面的函数调用都大同小异,这里是列出其中的一个: ; (baseObj.*pVIBaseMemFunc)(10);

mov esi, esp

push 10 ; 0000000aH

lea ecx, DWORD PTR _baseObj$[ebp]

call DWORD PTR _pVIBaseMemFunc$[ebp]

cmp esi, esp

call __RTC_CheckEsp

这里的汇编代码并没有给我们太多新鲜的内容:将对象的首地址(this指针)存放于寄存器ECX中,接着就将指令转到变量_pVIBaseMemFunc所占内存的前4个字节所表示的地址。

到了这里,我们应该对成员函数指针有了进一步的了解

以下为关联文档:

会计个人工作计划精选汇编会计个人工作计划精选汇编第一、参加财务人员继续教育每年财务人员都要参加财政局组织的财务人员继续教育,但是xx年11月底,继续教育教材全变,由于国家财务部最新发布公告:09...

2019年上半年工作总结汇编2019年上半年工作总结汇编一:通过日常工作积累我对工作岗位的认识采购部是公司运转的一个非常重要的环节,是公司内能够创造收益的部门,作为采购部的一个采购员,需弄清采购的客...

医生个人年终工作总结汇编医生个人年终工作总结汇编一、努力学习,不断提高政治理论水平和素质。在政治思想方面,始终坚持党的路线、方针、政策,认真学习马列主义、毛泽东思想和邓小平理论以及三个代表...

汇编会计个人工作总结精选汇编会计个人工作总结一、加强学习,注重提升个人修养一是透过杂志报刊、电脑网络和电视新闻等媒体,认真学习贯彻党的路线、方针、政策,深入学习领会党的十六大、十六届三...

C++技巧:OpenCASCADE智能指针的使用学习OCC的第一步是要了解其类的结构及组成,比如AIS_InteractiveObject类用来表示一个交互 式图形对象,如果进一步了解会发现其继承关系是:MMgt_TShared->Standard_Transient->P...

各种水污染指针之意义及影响指示水污染程度之水质指针,依性质大致可分为物理性、化学性及生物性三类指针,分别释述如下: 1.物理性的水污染指针之意义及影响 (1)水温: 表示水的冷热程度,常用℃表示。水温可影响...

考试辅导托福语法应试思路和考点汇编这是smart语法全真题的指导部分,我想应该是一个比较系统的介绍,希望对大家有帮助。至于例句我已经省略了,如果大家需要,我会再加的。:)大家做完一套卷子之后就看看,自己错在哪几个...

HELLOWORLD进阶汇编程序系列TITLE ***HELLO,WORLD进阶程序之选择分支 BY LLUCT*** DATA SEGMENT ;定义数据段 MSG1 DB '***WELE TO MY PROGRAM BY LLUCT***','$' ;定义输出的第一个字符串信息,字符串必...

汇编编写DOS下的内存驻留程序绪言 0.1 内存驻留与中断 内存驻留程序英文叫Terminate and Stay Resident Program,缩写为TSR.这些程序加载进内存,执行完后,就驻留在内存里,当满足条件时,调到前台来执行。...

推荐阅读
图文推荐