wpcockroach

导航

C++ Member Function Pointer

说一说C++的成员函数指针。

在刚学C++的时候,一个基本的概念是指针的大小等于机器的字长。因此在32位的机器上,一个指向整型的指针sizeof会返回4。那么,是不是意味所有类型的指针sizeof后,都是4呢?

一般来说,绝对的事情是比较少的,特殊情况总是存在的,那就是成员函数指针。我们从简单的例子一点一点来看这个问题。

案例一:

class CBase1 {};
typedef void (CBase1::*FPbase1)();
int main()
{
    size_t sizeFPbase1 = sizeof(FPbase1);
    cout << "sizeFPbase1 = " << sizeFPbase1 << endl;
}

OK,没啥特殊的,输出4。

再来看一个:

class CBase1 {};
class CDerive1 : public CBase1 {};
typedef void (CDerive1::*FPderive1)();
int main()
{
    size_t sizeDerive1 = sizeof(FPderive1);
    cout << "sizeDerive1 = " << sizeDerive1 << endl;
}

还是4。那什么时候成员函数指针大小才会不等于4呢?请看下面这个例子。

案例三:

class CBase1 {};
class CBase2 {};
class CDerive2 : public CBase1, public CBase2 {};
typedef void (CDerive2::*FPderive2)();
int main()
{
    size_t sizeDerive2 = sizeof(FPderive2);
    cout << "sizeDerive2 = " << sizeDerive2 << endl;
}

在VC下,你会发现sizeDerive2的大小是8,这是为什么呢?

一般来说,通用的解释就是:“这是有一定历史渊源的”。事实也确实如此。但是历史的事情,我就不说了,大家自己google好了。这里以VC为例,做一个简单介绍(WARNING:GCC等其他编译器在实现上是存在不同的)。

我们知道C++里有一个关键字叫this。对于CDerive1来说,这个类对象的this (简称CDerive1-this)和CBase1 (CBase1-this)是一样的。但是对于CDerive2-this来说,情况有所不同。如果是用来调用CBase1中的成员函数,没什么问题。但是当调用CBase2中的成员函数时,该怎么办?总不能拿CBase1-this来搞吧?

因此,在VC中,这个成员函数指针有两部分组成,一部分是CDerive2-this,还有一部分用于调整CDerive2-this。下面用一个简单的例子来证明下。

案例四:

int main()
{
    FPderive2 fpDerive2 = nullptr;
    FPbase2 fpBase2 = nullptr;

    fpDerive2 = fpBase2;
}

怎么证明呢?先来看汇编吧。

; 46   :     FPderive2 fpDerive2 = nullptr;

    mov    DWORD PTR $T5371[ebp], 0
    mov    DWORD PTR $T5371[ebp+4], 0

    mov    ecx, DWORD PTR $T5371[ebp]
    mov    DWORD PTR _fpDerive2$[ebp], ecx
    mov    edx, DWORD PTR $T5371[ebp+4]
    mov    DWORD PTR _fpDerive2$[ebp+4], edx

; 47   :     FPbase2   fpBase2 = nullptr;

    mov    DWORD PTR _fpBase2$[ebp], 0

; 48   :
; 49   :     fpDerive2 = fpBase2;

    mov    eax, DWORD PTR _fpBase2$[ebp]
    mov    DWORD PTR $T5372[ebp], eax
    mov    DWORD PTR $T5372[ebp+4], 1
    mov    ecx, DWORD PTR $T5372[ebp]
    mov    DWORD PTR _fpDerive2$[ebp], ecx
    mov    edx, DWORD PTR $T5372[ebp+4]
    mov    DWORD PTR _fpDerive2$[ebp+4], edx

首先看一下高亮的那几行。很明显地你会发现同样是初始化,fpDerive2和fpBase2产生的汇编码完全两样。fpDerive2占用了2个DWORD区域,而fpBase2只占用了1个DWORD。这里解释了sizeof为什么一个返回8一个返回4。

再看fpDerive2 = fpBase2。

; 49 : fpDerive2 = fpBase2;

    mov eax, DWORD PTR _fpBase2$[ebp]
    mov DWORD PTR $T5372[ebp], eax
    mov DWORD PTR $T5372[ebp+4], 1
    mov ecx, DWORD PTR $T5372[ebp]
    mov DWORD PTR _fpDerive2$[ebp], ecx

    mov edx, DWORD PTR $T5372[ebp+4]
    mov DWORD PTR _fpDerive2$[ebp+4], edx

注意高亮部分。这里我们看到,fpDerive2的高DWORD区域被置1了。如果我们的代码是fpDerive2 = fpBase1会是什么情况呢?试一下,你就会发现,fpDerive2的高DWORD区域被置0了。所以,我们有理由相信高DWORD区域是被用来存放一个调节变量的。

那么,低DWORD用来存放什么呢?自己试吧,简单说就是this。

这里,我们考虑的情况还只是多重继承。如果是虚继承又会发生什么情况?如果一个类只有一个前置声明,那么它的成员函数指针大小是多少?这些情况会更复杂。有兴趣的同学可以参考下面几篇文章做更细致的研究。

更新(4/12/2012):研究了下FastDelegates的代码,针对VC,好像我的理解还是有点偏差。

posted on 2012-04-05 23:04  wpcockroach  阅读(1055)  评论(0编辑  收藏  举报