Reputation: 153
I need to convert some member function pointers to void*
pointers (because I need to push them to the Lua stack, but the problem is not Lua related).
I do this using a union
. But when I convert the member function pointers to a void*
and back again and then try to call the pointer with an instance of the class, the this
pointer gets corrupted. Strangly, this problem doesn't happen, if I convert the void*
pointer back into a C-style function pointer with a pointer to the class as it's first parameter.
Here is piece of code that demonstrates the problem:
#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;
}
That is the output:
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
Is this a bug in the compiler (GCC)? I know that this conversion between void*
and (member) function pointers is not standard compliant, but the odd thing is, that it works, when converting the void*
to a C style function pointer.
Upvotes: 6
Views: 2999
Reputation: 279265
It looks to me as though on your implementation, the first size(void*)
bytes of an instance of the pointer-to-member-function type void (test::*) ()
just so happen, in this case, to be the address of a function in memory. As an implementation detail, that function is callable as if it were a free function with this
as the first parameter. That's why conv3
appears to work.
However, your luck has run out when trying to copy those first sizeof(void*)
bytes out into a different instance of the pointer-to-member-function type. The uninitialized garbage in the rest of conv2
, once interpreted as the rest of a pointer-to-member-function after the initial code address, has made something go wrong. I suspect there are some flags and offsets in there, to record information about virtual functions and multiple- and virtual- inheritance. When that information is incorrect, stuff goes wrong.
Upvotes: 1
Reputation: 182769
Add these two lines to your code and the answer will be clear:
cout << "sizeof(void*)=" << sizeof(conv1.ptr) << endl;
cout << "sizeof(test::*)=" << sizeof(conv1.func) << endl;
The reason is simple. Consider:
class Base1
{
public:
int x;
void Foo();
Base1();
};
class Base2
{
public:
float j;
void Bar();
Base2();
};
class Derived : public Base1, public Base2
{
Derived();
};
When you call Foo
on a Derived
, the this
pointer must point to Base1::x
. But when you call Bar
on a Derived
, the this
pointer must point to Base2::j
! So a pointer to a member function must include both the address of the function and an "adjustor" to correct the this
pointer to point to an instance of the correct type of class that the function expects as the this
pointer.
You are losing the adjuster, causing the this
pointer to be adjusted randomly.
Upvotes: 6
Reputation: 324
Weirdly enough, here (under VS2005), the 1st and 3rd calls works fine but the 2nd (with conv3) fails with the this being corrupted.
Upvotes: 1