首页 > 代码库 > 为什么 C++ 成员函数指针是 16 字节宽的

为什么 C++ 成员函数指针是 16 字节宽的

    当提及指针时,我们通常认为它是可以用void * 指针表示的在x86_64架构上占用8字节的东西。例如, 维基百科有一篇 关于x86_64的文章 中这样写道:

    Pushes and pops on the stack are always in 8-byte strides, and pointers are 8 bytes wide .

    从CPU的角度来看,指针就只是一个内存地址,并且x86_64中的所有内存地址用64位表示,所以8字节的假设是成立的。其实可以简单地通过打印不同类型的指针大小来得到这个结论。

    #include <iostream>

    int main() {

    std::cout 《

    "sizeof(int*)      == " 《 sizeof(int*) 《 "\n"

    "sizeof(double*)   == " 《 sizeof(double*) 《 "\n"

    "sizeof(void(*)()) == " 《 sizeof(void(*)()) 《 std::endl;

    }

    编译并运行这个程序,结果明确地说明了所有指针是8字节的:

    $ uname -i

    x86_64

    $ g++ -Wall ./example.cc

    $ ./a.out

    sizeof(int*)      == 8

    sizeof(double*)   == 8

    sizeof(void(*)()) == 8

    但是在 C++ 里就有这么一个例外 —— 指向成员函数的指针。

    更有趣的是,成员函数指针的大小正好是其他指针大小的两倍。通过下面的简单的程序就可以验证这一点,它会打印 “16”:

    #include <iostream>

    struct Foo {

    void bar() const { }

    };

    int main() {

    std::cout 《 sizeof(&Foo::bar) 《 std::endl;

    }

    难道是 Wikipedia 错了么?当然不是。对于所有硬件来说,所有指针依然还是 8 个字节的宽度。那成员函数指针到底是什么呢?它其实是 C++ 语言的一个特性,是一个不能与硬件(物理)地址一一对应的虚拟出来的地址。由于它是由 C++ 编译器在运行时来实现(把成员函数指针转换成实际的虚拟内存地址,还伴随其他的一些相关工作),这一特性会带来轻微的运行时开销从而导致性能损失。C++ 规范并不关心具体的语言实现,所以它对该类指针并未做过多说明。幸运的是 Itanium C++ ABI specification (安腾 C++ 应用二进制接口规范,致力于标准化 C++ 运行时的实现)除了对 virtual table(虚表),RTTI(运行时类型识别)和 exceptions(异常)的实现做了说明外,还在 §2.3 节对成员函数指针做了如下的说明:

    每一个指向成员函数的指针都是有如下两部分成:

    ptr:

    如果指针指向一个非虚成员函数,该字段就是一个简单的函数指针。曼哈顿娱乐城如果该指针指向的是一个虚函数成员,那么该字段的值是该虚函数成员在其虚表中位移值加 1,在 C++ 中用 ptrdiff_t 类型表示。0 值表示 NULL 指针,与下面的调整字段值无关。

    adj:

    当成员函数被调用时,this 指针所必须做的位置调整(译者注:这与 C++ 的对象内存模型有关,确保每个成员函数正确的访问其函数体内引用的各种函数成员,下面会有进一步的解释),在 C++ 中用 ptrdiff_t 类型表示。

    一个成员函数指针是 16 位的,因为除了需要 8 位字节来存储函数的地址外,还需要一个地址大小(8 字节)的字段来存储 this 指针位置如何调整的信息(常识: 每当一个非静态的成员函数被调用时,this 指针都会被编译器暗中传递给该函数,以便于在函数体内部通过该指针正确的访问调用对象的各类成员)。上面的 ABI 规范没有说清楚的是为什么以及什么时候需要对 this 指针的位置做调整。

推荐阅读http://www.yupoo.com/photos/summo2/93898660/ 
http://www.cnblogs.com/huangdj/p/4142633.html 

为什么 C++ 成员函数指针是 16 字节宽的