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

前言:在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 < void *> (pVIBaseMemFunc);            // error
     int *   pInt   =  reinterpret_cast < int *> (pVIBaseMemFunc);             // error
  pVIDeriveMemFunc  =  static_cast < PVIDERIVEMEMFUNC > (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 *  argv[])
{
    PVIBASEMEMFUNC pVIBaseMemFunc 
=   & Base::setValue;
    PVIDERIVEMEMFUNC pVIDeriveMemFunc 
=  static_cast < PVIDERIVEMEMFUNC > (pVIBaseMemFunc);

    PVVBASEMEMFUNC      pVVBaseMemFunc   
=   & Base::foobar;
    PVVDERIVEMEMFUNC    pVVDeriveMemFunc 
=  static_cast < PVVDERIVEMEMFUNC > (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
_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。成员函数指针的转化。本文所采用的代码是想比较普通成员函数指针和虚函数指针在转化的过程中存在那些差异:
    ; PVIBASEMEMFUNC pVIBaseMemFunc  =   & Base::setValue;
    mov    DWORD PTR _pVIBaseMemFunc$[ebp], OFFSET FLAT:
? setValue@Base@@QAEXH@Z ;
    取出Base::setValue函数的地址,存放于变量pVIBaseMemFunc所占内存的前4个字节(DWORD)中。

; PVVBASEMEMFUNC      pVVBaseMemFunc    =   & Base::foobar;
mov    DWORD PTR _pVVBaseMemFunc$[ebp], OFFSET FLAT:
?? _9@$B3AE ; `vcall '
取出符号” ?? _9@$B3AE“的值,存放于变量pVVBaseMemFunc所占内存的前4个字节(DWORD)中。
 
对于符号”??_9@$B3AE“,我又找到了这样的汇编代码:
    _TEXT    SEGMENT
    
?? _9@$B3AE PROC NEAR                    ; `vcall ' , COMDAT
    mov    eax, DWORD PTR [ecx]
    jmp    DWORD PTR [eax
+ 4 ]
    
?? _9@$B3AE ENDP                        ; `vcall '
    _TEXT    ENDS
符号”
?? _9@$B3AE“代表的应该是一个存根函数,这个函数首先根据this指针获得虚函数表的指针,然后将指令再跳转到相应的虚函数的地址。
由此可以看出,对于虚函数,即使是用过成员函数指针间接调用,仍然具有和直接调用一样的特性。

    ; PVIDERIVEMEMFUNC pVIDeriveMemFunc  =  static_cast < PVIDERIVEMEMFUNC > (pVIBaseMemFunc);
    mov    eax, DWORD PTR _pVIBaseMemFunc$[ebp]
    mov    DWORD PTR _pVIDeriveMemFunc$[ebp], eax
直接将变量pVIBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量_pVIDeriveMemFunc所占内存的前4个字节中。

    ; PVVDERIVEMEMFUNC    pVVDeriveMemFunc  =  static_cast < PVVDERIVEMEMFUNC > (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个字节所表示的地址。

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

历史:
11/25/2006 
v1.0
原文的第一个正式版

参考文献:
1。
《C++ Common Knowledge: Essential Intermediate Programming》By Stephen C. Dewhurst

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1413669

 

在C++平静的海面底下,隐藏着许许多多暗礁,而类成员函数指针绝对是其中最险恶的之一。所以如果不幸碰到它,那么一定要打醒十二分精神,这是我的小小体会。

    下面来看看一个简单的例子。
    class Base {
    public:
        Base () : f_(0) {}
        virtual ~Base() {}
    public:
        typedef void (Base::*FUNC)();
        void setf (FUNC f)
        {
            f_ = f;
        }
        FUNC getf ()
        {
            return f_;
        }
    private:
        FUNC f_;
    
    };
    
    class Derived : public Base {
    public:
        void test1 ()
        {
            cout << "Derived: Test 1" << endl;
        }
       
        void test2 ()
        {
            cout << "Derived: Test 2" << endl;
        }
    
    };
 
    这里定义了两个类Base和Derived。其中Base类定义了一个类成员函数指针,看看它的语法,还是蛮怪异的:
    typedef void (Base::*FUNC)();
    因此FUNC就成了一个新的类型,这个类型的含义就是指向类成员函数的指针,而该类成员函数不需要入参,返回值是void。然后我们可以用FUNC来定义变量,当成Base的私有成员,并且定义了get、set函数来对它进行存取。
    接着我们来定义一个Derived类,从Base继承而来。在Derived类中定义了若干方法test1和test2,注意这些test1()和test2()必须符合FUNC的签名,也就是说,函数的入参和返回值必须一致。
    我们可以来写代码:
    Base *p = new Derived;
    p->setf (&Derived::test1);
    我本意是想把Derived的成员函数赋值给f_保存起来,结果编译了一下,VC6编译器不高兴了:
   
    error C2664: 'setf' : cannot convert parameter 1 from 'void (__thiscall Derived::*)(void)' to 'void (__thiscall Base::*)(void)'
        Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
 
    OK,原来我在写&Derived::test1时,其实它的类型是void (__thiscall Derived::*)(void),而定义在Base中的成员变量f_,它的类型是FUNC,也就是void (__thiscall Base::*)(void),这两者之间没有隐式转换的关系,必须显式进行转换。我改了一下,变成:
    p->setf (static_cast<Base::FUNC>(&Derived::test1));
    加了一个static_cast,就可以顺利转换了。
 
    最后我想把f_保存的类成员函数指针取出来进行调用,我们知道又要用到比较怪异的语法:
    (p->*f)();
    这个f就是我们取出来的成员函数指针:f = p->getf();
    合起来就是:(p->*(p->getf()))(); 够复杂吧?
 
    类成员函数首先是一个函数指针,这已经是一层间接性;然后它还是在类里面,这又多了一层间接性,因此要通过类成员函数指针来调用成员函数,就必须提供对象的指针,还要提供成员函数的指针,所以它的使用还是蛮复杂的。
    关于类成员函数指针,其实还有很多话题。例如Boost里面就有一个function库,对所有形式的函数,象静态函数,类成员函数,函数对象等,提供了统一的接口,可以说是一个泛型的函数类型。有时间希望能对它深入研究一下!

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=470323

C++的成员变量指针和普通的指针没有什么大的区别,int类型成员变量的指针就是和int *, Foo类型的成员变量的指针就是Foo *,但是成员函数就不大一样了,主要是因为非静态成员函数实际上都隐藏这一个调用对象的指针的参数。

静态成员函数指针,和普通的C函数指针没什么两样
class Foo
{
public:
    static int foo();
};

int (*fp)() = Foo::foo();
因为静态成员函数的执行和类的对象无关,也没有隐藏的对象指针参数

非静态成员函数,因为有隐藏的对象指针参数,就有点不一样
class Foo
{
     int foo1();
     int foo2(long);
};
为了代码的简洁,最好用typedef先定义指针类型
typedef int (Foo::*fpType1)(); //Foo类返回类型为int,参数列表为空的成员函数指针类型
typedef int (Foo::*fpType2)(long); //Foo类返回类型为int,参数列表为long的成员函数指针类型
然后就可以声明指向成员函数的指针
fpType1 fp1 = &Foo::foo1;
fpType2 fp2 = &Foo::foo2;
通过成员函数调用的时候注意,因为非静态的成员函数执行实际上是需要一个类对象的
Foo f;
int result = (f.*fp1)();
或者通过对象指针
Foo f2 = &f;
int result2 = (f2->*fp1)();

类对象的成员函数能够提供灵活的对成员函数的调用,今天写一个测试程序,为了这个测试程序的“扩展性”,研究成员函数指针花了一些时间,最后还是用别的方法来实现“扩展性”。虽然C++提供了这种个功能,我觉得还是尽量不要使用的好,程序不大容易读,就算我懂,将来维护代码的同僚未必明白,所以成员函数指针,能不用还是不用的好。

Trackback:  http://tb.blog.csdn.net/TrackBack.aspx?PostId=404960

在C++平静的海面底下,隐藏着许许多多暗礁,而类成员函数指针绝对是其中最险恶的之一。所以如果不幸碰到它,那么一定要打醒十二分精神,这是我的小小体会。

    下面来看看一个简单的例子。
    class Base {
    public:
        Base () : f_(0) {}
        virtual ~Base() {}
    public:
        typedef void (Base::*FUNC)();
        void setf (FUNC f)
        {
            f_ = f;
        }
        FUNC getf ()
        {
            return f_;
        }
    private:
        FUNC f_;
    
    };
    
    class Derived : public Base {
    public:
        void test1 ()
        {
            cout << "Derived: Test 1" << endl;
        }
       
        void test2 ()
        {
            cout << "Derived: Test 2" << endl;
        }
    
    };
 
    这里定义了两个类Base和Derived。其中Base类定义了一个类成员函数指针,看看它的语法,还是蛮怪异的:
    typedef void (Base::*FUNC)();
    因此FUNC就成了一个新的类型,这个类型的含义就是指向类成员函数的指针,而该类成员函数不需要入参,返回值是void。然后我们可以用FUNC来定义变量,当成Base的私有成员,并且定义了get、set函数来对它进行存取。
    接着我们来定义一个Derived类,从Base继承而来。在Derived类中定义了若干方法test1和test2,注意这些test1()和test2()必须符合FUNC的签名,也就是说,函数的入参和返回值必须一致。
    我们可以来写代码:
    Base *p = new Derived;
    p->setf (&Derived::test1);
    我本意是想把Derived的成员函数赋值给f_保存起来,结果编译了一下,VC6编译器不高兴了:
   
    error C2664: 'setf' : cannot convert parameter 1 from 'void (__thiscall Derived::*)(void)' to 'void (__thiscall Base::*)(void)'
        Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
 
    OK,原来我在写&Derived::test1时,其实它的类型是void (__thiscall Derived::*)(void),而定义在Base中的成员变量f_,它的类型是FUNC,也就是void (__thiscall Base::*)(void),这两者之间没有隐式转换的关系,必须显式进行转换。我改了一下,变成:
    p->setf (static_cast<Base::FUNC>(&Derived::test1));
    加了一个static_cast,就可以顺利转换了。
 
    最后我想把f_保存的类成员函数指针取出来进行调用,我们知道又要用到比较怪异的语法:
    (p->*f)();
    这个f就是我们取出来的成员函数指针:f = p->getf();
    合起来就是:(p->*(p->getf()))(); 够复杂吧?
 
    类成员函数首先是一个函数指针,这已经是一层间接性;然后它还是在类里面,这又多了一层间接性,因此要通过类成员函数指针来调用成员函数,就必须提供对象的指针,还要提供成员函数的指针,所以它的使用还是蛮复杂的。
    关于类成员函数指针,其实还有很多话题。例如Boost里面就有一个function库,对所有形式的函数,象静态函数,类成员函数,函数对象等,提供了统一的接口,可以说是一个泛型的函数类型。有时间希望能对它深入研究一下!

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=470323

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值