2012-06-11 22 views
5

Necesito convertir algunos punteros a funciones miembro para void* punteros (porque necesito para empujarlos a la Lua apilar, pero el problema no es Lua relacionado).Llamar a un ++ puntero de función miembro de C: este puntero se corrompe

Lo hago usando un union. Pero cuando convierto los punteros de función de miembro a void* y viceversa y luego intento llamar al puntero con una instancia de la clase, el puntero this se daña. Extrañamente, este problema no ocurre, si convierto el puntero void* en un puntero a función C con un puntero a la clase como su primer parámetro.

Aquí es una pieza de código que muestra el problema:

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

Esa es la salida:

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 

Es esto un error en el compilador (GCC)? Sé que esta conversión entre void* y punteros de función (miembro) no es compatible con el estándar, pero lo curioso es que funciona al convertir el void* en un puntero de función de estilo C.

+3

¿Has comprobado el tamaño de() de tu memfunptr? En mi implementación es mayor que sizeof (void *) – PlasmaHH

Respuesta

5

Añadir estas dos líneas a su código y la respuesta será clara:

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

La razón es simple. Considere:

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

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

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

Cuando se llama a Foo en un Derived, el puntero this debe apuntar a Base1::x. Pero cuando llama al Bar en un Derived, el puntero this debe apuntar a Base2::j! Por lo tanto, un puntero a una función miembro debe incluir tanto la dirección de la función como un "ajustador" para corregir el puntero this para que apunte a una instancia del tipo correcto de clase que la función espera como el puntero this.

Está perdiendo el ajustador, lo que hace que el puntero this se ajuste aleatoriamente.

+0

Ok, muchas gracias, eso dejó en claro. Ahora convierto los punteros usando otro 'union {struct {void * p1, * p2; } ptr; void (test :: * func)(); } ' ¿Funcionará esto en cualquier caso? ¿O sería mejor buscar primero el 'sizeof' del puntero a la función miembro y luego decidir cuántos' void * 'usar? –

+0

Me gusta su primer acercamiento mejor. –

+1

@AlfredKrohmer: probablemente debería asignar la función de puntero a miembro, pasarle un puntero y descubrir la mejor forma de administrar el recurso. Seguramente si estuvieras pasando algo de 'struct' a través de Lua, que contiene algunos enteros y un puntero, no tratarías de calcular cuántos' void * 'son necesarios para sumar a su memoria, y la reinterpretarán como' 'void * '? Bueno, una función de puntero a miembro es unos pocos enteros y un puntero. Tenga en cuenta también que en algunas implementaciones, los punteros a diferentes tipos de funciones de miembros son de diferentes tamaños. –

1

Curiosamente, aquí (en VS2005), la 1ª y la 3ª llamadas funcionan bien pero la 2ª (con conv3) falla y esta está dañada.

+1

Como una conjetura voladora, VS2005 usa la convención de llamadas cdecl para el puntero a función (por lo que el parámetro se pasa en la pila), pero la convención de llamada thiscall con funciones miembro (por lo que el destinatario espera que 'this' se pase en ECX). De modo que evita que 'conv3' funcione incluso si surgió con la dirección correcta del código. –

+0

Este es el comportamiento exacto que esperaba que sucediera. –

1

Me parece como si en su implementación, el primer size(void*) bytes de una instancia del tipo de función de puntero a miembro void (test::*)() simplemente sucede que, en este caso, es la dirección de una función en la memoria. Como detalle de implementación, esa función se puede llamar como si fuera una función libre con this como primer parámetro. Es por eso que conv3 parece funcionar.

Sin embargo, su suerte se ha agotado al intentar copiar los primeros sizeof(void*) bytes en una instancia diferente del tipo de función de puntero a miembro. La basura no inicializada en el resto de conv2, una vez interpretada como el resto de una función de puntero a miembro después de la dirección de código inicial, ha hecho que algo salga mal. Sospecho que hay algunas banderas y compensaciones allí, para registrar información sobre funciones virtuales y herencia múltiple y virtual. Cuando esa información es incorrecta, las cosas salen mal.

Cuestiones relacionadas