【问题标题】:Why the size of a pointer to a function is different from the size of a pointer to a member function?为什么指向函数的指针的大小与指向成员函数的指针的大小不同?
【发布时间】:2012-08-13 23:09:23
【问题描述】:

指针不就是地址吗?还是我遗漏了什么?

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

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

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

三件事:

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

【问题讨论】:

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


【解决方案1】:

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

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;};

它可能是这样布置的:

如您所见,如果您想用A*C* 指向对象,则指向开始,但如果您想用B* 指向它,则必须指向中间的某个地方。

所以如果CB继承了一些成员函数并且你想指向它然后调用C*上的函数,它需要知道洗牌this指针。该信息需要存储在某个地方。所以它与函数指针混为一谈。

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

struct A
{
    int a;
    virtual void foo(){};
};
struct B : A
{
    int b;
    virtual void foo(){};
    virtual void bar(){};
};

编译器最终可能会变成这样:

所以指向虚函数的成员函数指针实际上需要成为虚表的索引。 所以成员函数指针实际上需要 1) 可能是函数指针,2) 可能是 this 指针的调整,以及 3) 可能是 vtable 索引。为了保持一致,每个成员函数指针都需要具备所有这些功能。所以这是指针的8字节,调整的4字节,索引的4字节,总共16字节。

我相信这实际上在编译器之间会有很大差异,并且有很多可能的优化。可能没有人真正按照我描述的方式实现它。

很多详细信息,请参阅this(滚动到“成员函数指针的实现”)。

【讨论】:

  • 我冒昧地插入了我认为您正在寻找的标准中的文本。我希望没关系。
  • 请注意,就标准而言,int* 可能小于void*;对于一些非常古老的 C 实现,确实如此。 void* 保证的所有内容都是非成员数据:没有什么可以保证,例如,指向成员数据的指针不大于 void*(尽管我无法想象一个合理的实现,它会是)。
  • 这:将被实现为指向对象的指针 显然是不正确的。对象指针在方法被调用的地方提供。但我认为你只是把你的话弄错了。我只是重新表述它以表明需要更多信息来支持多态行为。
  • 我认为 foo(&a); 应该是 A_foo(&a); 在您的代码行中:A a; foo(&a);
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    一些解释可以在这里找到: The underlying representation of member function pointers

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • :) 我也是这么想的,weggo,但事实并非如此。例如,因为初始化此类指针与任何对象无关,而仅与类声明有关。这表明我错了。
      【解决方案5】:

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

      • 它在编译器中的实现比将指向成员函数的指针实现为 T (*f)() 更简单

      • 它不涉及运行时代码生成或额外的簿记

      • T (*f)()相比,它的表现相当不错

      • C++ 程序员对于指向成员函数的指针大小等于sizeof(void*)

      • 的需求不足
      • 在执行期间生成运行时代码实际上是当前 C++ 代码的禁忌

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多