有没有办法获得C ++虚拟成员函数地址

时间:2013-02-16 13:06:07

标签: c++ function virtual

我搜索了这篇文章: 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. 为什么&Base::foo没有获得正确的C ++成员地址?

3 个答案:

答案 0 :(得分:5)

  

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

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

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

  

指向成员的指针不应指向**类(9.4)的静态成员,具有引用类型的成员或“cv void”。   [注:另见5.3和5.5。 类型“指向成员的指针”与“指针”类型不同,也就是说,指向成员的指针仅由指向成员声明符语法的指针声明,而不是由指针声明符语法声明。 C ++中没有“reference-to-member”类型。 - 后注]

此外,第5.2.10 / 10段(关于reinterpret_cast<>)定义了指向成员的指针的唯一可能转换,如下所示:

  

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

     

- 将“指向成员函数的指针”类型的prvalue转换为指向成员函数类型的不同指针,并返回其原始类型,从而生成指向成员值的原始指针。

     

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

答案 1 :(得分:1)

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

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

对于第三行,编译器获取f的地址。将它转换为Base对象的地址,在这种情况下是一个NOOP)从该地址读取v的vtable的地址(vtable通常是对象中的第一个数据项),然后添加两个地址1 * sizeof(void *)并在该地址调用函数,大致相当于这个C:

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

这只是假设,对于您拥有的编译器的确切版本而言可能是正确的。即使对未来的编译器版本也不保证可移植性。

答案 2 :(得分:0)

  

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

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