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

出处: 作者: 日期:2007年11月28日 10时50分

 成功编译后生成汇编代码。老规矩,在分析汇编代码的过程中还是只分析对解决问题有意义的汇编代码,其他的就暂时忽略。
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个字节所表示的地址。

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

最后更新时间:2008-11-05 04:55:42
文章评论
共有 0 位网友发表了评论
用户名: 新注册) 密码: 匿名评论 [查看所有评论]

评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。

考试全流程