【问题标题】:Is there a way to get the C++ virtual member function address有没有办法获取C++虚成员函数地址
【发布时间】:2013-02-01 08:49:09
【问题描述】:

我搜索了这篇文章: C++ : Getting function virtual 'address' with member function pointer

为了测试虚拟成员函数是否通常在对象的起始地址,我写了如下代码:

#include <pwd.h>
#include <string.h>
#include <stdio.h>

class Base
{
public:
    int mBase1;
    char mBase2;
    virtual void foo()
    {
        fprintf(stderr,"Base foo called\n");
    }
};

class Child: public Base
{
public:
    int cc;
    virtual void foo()
    {
        fprintf(stderr,"Child foo called");
    }
};


int main(int argc, char *argv[])
{
    Base bb;
    fprintf(stderr,"[mBase1 %p][mBase2 %p] [foo %p]\n",&bb.mBase1,&bb.mBase2,&Base::foo);
    return 0;
}

编译时出现警告:

test.cpp:30:88: warning ‘%p’ expects argument of type ‘void*’, but argument 5 has type ‘void (Base::*)()’ [-Wformat]

输出是:

[mBase1 0xbfc2ca38][mBase2 0xbfc2ca3c] [foo 0x1]

我认为它是有线的。

  1. 是否有任何“漂亮的方法”来获取成员函数(不是静态成员函数地址?)。除了下面的方法,还有其他优雅的方法吗?

    typedef void (Base::*Foo)();
    Foo f = &Base::foo();
    
  2. 为什么&amp;Base::foo没有得到正确的C++成员地址?

【问题讨论】:

  • 函数本身不会在对象的开头;指向 v 表的指针(通常)将位于对象的开头,并且在指向的表中,您会找到指向函数的指针(但不是函数本身)。
  • “普通”指针,包括函数指针,通常表示为机器地址;成员指针不是。考虑Derived d; (d.*f)(); 工作,但调用Derived::Foo 正如人们所期望的那样。如果f一个特定地址,你就不能这样做。

标签: c++ function virtual


【解决方案1】:

为什么 &Base::foo 没有得到正确的 C++ 成员地址?

指向成员的指针与指针是不同的类型(实际上,它们比类型本身具有更多的偏移量),特别是它们不能转换为void*(它们可以在 GCC 上,但它可能是编译器扩展)。

参见 C++11 标准的第 8.3.3/3 段:

指向成员的指针不应指向**类 (9.4) 的静态成员、具有引用类型的成员或“cv void”。 [注:另见 5.3 和 5.5。 “指向成员的指针”类型与“指针”类型不同,即指向成员的指针仅通过指向成员的声明符语法声明,而从不通过指针声明符语法声明。 C++ 中没有“成员引用”类型。 ——尾注]

此外,第 5.2.10/10 段(关于 reinterpret_cast&lt;&gt;)定义了指向成员的指针的唯一可能转换如下:

如果 T1 和 T2 都是函数类型或都是对象类型,则“指向 T1 类型 X 成员的指针”类型的纯右值可以显式转换为不同类型“指向 T2 类型 Y 成员的指针”的纯右值.空成员指针值 (4.11) 被转换为目标类型的空成员指针值。此转换的结果未指定,但以下情况除外:

——将“指向成员函数的指针”类型的纯右值转换为指向成员函数类型的不同指针并返回其原始类型会产生指向成员值的原始指针。

— 将类型“指向 T1 类型 X 的数据成员的指针”的纯右值转换为“指向类型 T2 的 Y 数据成员的指针”类型(其中 T2 的对齐要求不比 T1 的对齐要求更严格)和回到它的原始类型会产生指向成员值的原始指针。

【讨论】:

    【解决方案2】:

    如前所述,指向成员的指针不是指针,它们转换为 int 或 void * 等类型通常是没有意义的。特别是 0x1 最有可能表示对象 vtable 中的第一个或第二个条目。 让我们考虑一下这段代码:

    void (*Base::*test)() = &Base::foo
    Child f;
    f.*test();
    

    对于第三行,编译器获取 f 的地址。将其转换为 Base 对象的地址,在本例中为 NOOP)从该地址读取 f 的 vtable 的地址(vtable 通常是对象中的第一个数据项),然后添加两个该地址 1* sizeof(void*) 并在该地址调用函数,这在很大程度上与此 C 等效:

    typedef void (*FnPtr)();
    FnPtr *vtable=*(FnPtr **)&(Base &)f;
    (*(vtable[(int)test]))(); or (*(vtable[(int)test - 1]))();
    

    这只是对您所拥有的编译器的确切版本可能正确的假设。不保证可移植到未来的编译器版本。

    【讨论】:

      【解决方案3】:

      为什么 &Base::foo 没有得到正确的 C++ 成员地址?

      Base::foo 不是静态方法。没有属于 Base 的 foo。我想知道 [foo 0x1] 是什么意思(例如:为什么 0x1 如果整个事情没有多大意义)?

      【讨论】:

        猜你喜欢
        • 2011-06-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-18
        • 1970-01-01
        相关资源
        最近更新 更多