为什么指向函数的指针大小与指向成员函数的指针大小不同?

时间:2012-08-17 13:33:59

标签: c++ pointers function-pointers member-function-pointers

指针只是一个地址吗?或者我错过了什么?

我测试了几种类型的指针:

  • 指向任何变量的指针是相同的(我平台上的8B)
  • 指向函数的指针大小相同,指向变量的指针(8B再次)
  • 指向具有不同参数的函数的指针 - 仍然相同(8B)

但指向成员函数的指针更大 - 我平台上的16B。

三件事:

  1. 为什么指向成员函数的指针更大?他们还需要更多信息吗?
  2. 据我所知,标准没有说明指针的大小,除了 void*必须能够“包含”任何指针类型。换句话说,任何指针都必须能够转换为void*,对吗?如果是,那么为什么sizeof( void* )是8,而sizeof指向成员函数的指针是16?
  3. 是否有其他指针示例,它们的大小不同(我的意思是,对于标准平台,而不是一些罕见和特殊的平台)?

5 个答案:

答案 0 :(得分:28)

编辑:所以我注意到这几个月后我仍然得到了投票,尽管我原来的答案很糟糕且误导(我甚至不记得当时的想法,它没有多大意义!)所以我想我会尝试澄清情况,因为人们仍然必须通过搜索来到这里。

在最正常的情况下,你几乎可以想到

struct A {
    int i;
    int foo() { return i; }
};

A a;
a.foo();

作为

struct A {
    int i;
};
int A_foo( A* this ) { return this->i; };

A a;
A_foo(&a);

(开始看起来像C,对吧?)所以你会认为指针&A::foo与普通函数指针一样。但是有一些复杂问题:多重继承和虚函数。

想象一下我们有:

struct A {int a;};
struct B {int b;};
struct C : A, B {int c;};

它可能是这样的:

Multiple inheritance

如您所见,如果您想指向具有A*C*的对象,则指向开头,但如果您想指向{{1你必须指向中间的某个地方。

因此,如果B*C继承某个成员函数并且您想要指向它,然后在B上调用该函数,则需要知道要对{{1}进行洗牌指针。这些信息需要存储在某个地方。所以它被函数指针集中。

现在,对于每个具有C*函数的类,编译器会创建一个名为虚拟表的列表。然后,它会向该类添加一个额外的指针( vptr )。所以对于这个类结构:

this

编译器最终可能会像这样: enter image description here

因此,指向虚函数的成员函数指针实际上需要是虚拟表的索引。 因此,成员函数指针实际上需要1)可能是函数指针,2)可能调整virtual指针,3)可能是vtable索引。为了保持一致,每个成员函数指针都需要能够满足所有这些要求。因此,指针为struct A { int a; virtual void foo(){}; }; struct B : A { int b; virtual void foo(){}; virtual void bar(){}; }; 个字节,调整为this个字节,索引为8个字节,总共为4个字节。

我认为这在编译器之间实际上有很大不同,并且有很多可能的优化。可能没有人按照我描述的方式实现它。

有关 lot 的详细信息,请参阅this(滚动到“会员功能指针的实现”)。

答案 1 :(得分:6)

基本上是因为他们需要支持多态行为。看看Raymond Chen的好article

答案 2 :(得分:2)

可在此处找到一些解释: The underlying representation of member function pointers

  

尽管指向成员的指针表现得像普通指针,但在幕后它们的表现形式却截然不同。事实上,指向成员的指针通常由一个结构组成,在某些情况下最多包含四个字段。这是因为指向成员的指针不仅要支持普通的成员函数,还要支持虚拟成员函数,具有多个基类的对象的成员函数以及虚基类的成员函数。因此,最简单的成员函数可以表示为一组两个指针:一个保存成员函数的物理内存地址,另一个指针保存该指针。但是,在虚拟成员函数,多继承和虚拟继承的情况下,指向成员的指针必须存储其他信息。因此,您不能将指向成员的指针转换为普通指针,也不能安全地在指向不同类型成员的指针之间进行转换。   块引用

答案 3 :(得分:0)

我猜它与this指针有关...也就是说,每个成员函数也必须有它们所在类的指针。然后指针使函数变大一点大小

答案 4 :(得分:0)

将指向成员函数的指针表示为{this, T (*f)()}的一些主要原因是:

  • 在编译器中实现比在T (*f)()

  • 实现成员函数的指针更简单
  • 它不涉及运行时代码生成,也不涉及额外的簿记

  • T (*f)()

  • 相比,它表现得相当不错
  • C ++程序员对成员函数指针大小等于sizeof(void*)的要求不够

  • 执行期间运行时代码生成事实上是目前C ++代码的禁忌