5

我需要将一些成员函数指针转换为void*指针(因为我需要将它们推送到 Lua 堆栈,但问题与 Lua 无关)。

我使用union. 但是,当我将成员函数指针转换为 avoid*并再次转换回来,然后尝试使用类的实例调用指针时,this指针会损坏。void*奇怪的是,如果我将指针转换回 C 风格的函数指针,并将指向类的指针作为它的第一个参数,那么这个问题就不会发生。

这是演示问题的一段代码:

#include <iostream>
using namespace std;

class test
{
    int a;

    public:
        void tellSomething ()
        {
            cout << "this: " << this << endl;
            cout << "referencing member variable..." << endl;
            cout << a << endl;
        }
};

int main ()
{
    union
    {
        void *ptr;
        void (test::*func) ();
    } conv1, conv2;

    union
    {
        void *ptr;
        void (*func) (test*);
    } conv3;

    test &t = *new test ();

    cout << "created instance: " << (void*) &t << endl;

    // assign the member function pointer to the first union
    conv1.func = &test::tellSomething;

    // copy the void* pointers
    conv2.ptr = conv3.ptr = conv1.ptr;

    // call without conversion
    void (test::*func1) () = conv1.func;
    (t.*func1) (); // --> works

    // call with C style function pointer invocation
    void (*func3) (test*) = conv3.func;
    (*func3) (&t); // --> works (although obviously the wrong type of pointer)

    // call with C++ style member function pointer invocation
    void (test::*func2) () = conv2.func;
    (t.*func2) (); // `this' is the wrong pointer; program will crash in the member function

    return 0;
}

那就是输出:

created instance: 0x1ff6010
this: 0x1ff6010
referencing member variable...
0
this: 0x1ff6010
referencing member variable...
0
this: 0x10200600f
referencing member variable...
zsh: segmentation fault (core dumped)  ./a.out

这是编译器(GCC)中的错误吗?我知道void*和(成员)函数指针之间的这种转换不符合标准,但奇怪的是,它在转换void*为 C 风格的函数指针时有效。

4

3 回答 3

5

将这两行添加到您的代码中,答案将一目了然:

cout << "sizeof(void*)=" << sizeof(conv1.ptr) << endl;
cout << "sizeof(test::*)=" << sizeof(conv1.func) << endl;

原因很简单。考虑:

class Base1
{
 public:
 int x;
 void Foo();
 Base1();
};

class Base2
{
 public:
 float j;
 void Bar();
 Base2();
};

class Derived : public Base1, public Base2
{
 Derived();
};

当您调用FooaDerived时,this指针必须指向Base1::x。但是当你调用BaraDerived时,this指针必须指向Base2::j! 因此,指向成员函数的指针必须同时包含函数的地址和“调整器”,以更正this指针以指向函数期望作为this指针的正确类型的类的实例。

您正在丢失调整器,导致this指针随机调整。

于 2012-06-11T10:17:38.750 回答
1

奇怪的是,在这里(在 VS2005 下),第 1 次和第 3 次调用工作正常,但第 2 次(使用 conv3)失败,因为 this 被破坏了。

于 2012-06-11T10:20:58.350 回答
1

在我看来,就好像在您的实现中,size(void*)指向成员函数类型的实例的第一个字节void (test::*) ()恰好发生在这种情况下,是内存中函数的地址。作为实现细节,该函数是可调用的,就好像它是this作为第一个参数的自由函数一样。这就是为什么conv3似乎有效。

sizeof(void*)但是,当您尝试将这些第一个字节复制到指向成员函数类型的不同实例中时,您的运气已经耗尽。其余部分中的未初始化垃圾conv2,一旦被解释为初始代码地址之后的指向成员函数的其余部分,就会出错。我怀疑那里有一些标志和偏移量,用于记录有关虚拟功能以及多重继承和虚拟继承的信息。当该信息不正确时,事情就会出错。

于 2012-06-11T10:24:34.917 回答