2012-06-11 14 views
5

muszę konwertować kilka wskaźników funkcji member aby void* wskaźników (bo muszę pchnąć je do Lua stos, ale problem nie jest Lua podobne).Wywoływanie C++ wskaźnik funkcji użytkownika: to-pointer zostanie uszkodzona

Robię to za pomocą union. Ale kiedy konwertuję wskaźniki funkcji składowej na void* i ponownie, a następnie spróbuję wywołać wskaźnik za pomocą klasy klasy, wskaźnik this zostanie uszkodzony. Co dziwne, ten problem nie występuje, jeśli skonwertuję wskaźnik void* z powrotem na wskaźnik funkcji w stylu C ze wskaźnikiem do klasy jako jej pierwszym parametrem.

Oto fragment kodu, który demonstruje 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; 
} 

czyli wyjście:

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 

Jest to błąd w kompilator (GCC)? Wiem, że konwersja pomiędzy wskaźnikami funkcji void* i (członkiem) nie jest zgodna ze standardem, ale dziwne jest to, że działa, konwertując void* na wskaźnik funkcji stylu C.

+3

Czy sprawdziłeś sizeof() swojego memfunptr? W mojej implementacji jest większy niż sizeof (void *) – PlasmaHH

Odpowiedz

5

Dodaj te dwie linie w kodzie, a odpowiedź będzie jasna:

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

Powód jest prosty. Rozważmy:

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

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

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

Po wywołaniu Foo na Derived, wskaźnik this musi wskazywać Base1::x. Ale po wywołaniu Bar na Derived, wskaźnik this musi wskazywać na Base2::j! Tak więc wskaźnik do funkcji składowej musi zawierać zarówno adres funkcji, jak i "regulator", aby skorygować wskaźnik, tak aby wskazywała na wystąpienie właściwego typu klasy, którego funkcja oczekuje jako wskaźnika.

Tracisz regulator, powodując losowe dopasowanie wskaźnika this.

+0

Dobrze, dziękuję bardzo, to jasno. Konwertuję wskaźniki teraz używając innego 'union {struct {void * p1, * p2; } ptr; void (test :: * func)(); } '. Czy to zadziała w każdym przypadku? A może lepiej byłoby najpierw spojrzeć w górę 'sizeof' na wskaźnik funkcji member, a następnie zdecydować ile' void * 'użyć? –

+0

Lubię twoje pierwsze podejście lepiej. –

+1

@AlfredKrohmer: prawdopodobnie powinieneś po prostu przydzielić funkcję wskaźnika do członka, przekazać do niej wskaźnik i dowiedzieć się, jak najlepiej zarządzać zasobem. Na pewno, jeśli przekazywałeś jakąś "struct" przez Lua, zawierającą kilka liczb całkowitych i wskaźnik, nie próbowałbyś ustalić, ile "voidów" jest wymaganych do dodania do swojej pamięci i reinterpretować ją jako kilka "pustek" *? Cóż, funkcja wskaźnika do członka to kilka liczb całkowitych i wskaźnik. Należy również pamiętać, że w niektórych implementacjach wskaźniki dla różnych rodzajów funkcji składowych są różne. –

1

Co dziwne, tutaj (pod VS2005), pierwsze i trzecie połączenie działa dobrze, ale druga (z conv3) kończy się niepowodzeniem z tym, że jest uszkodzony.

+1

Jako latający domysł, VS2005 używa konwencji wywoływania cdecl dla wskaźnika do funkcji (tak, że parametr jest przekazywany na stosie), ale ta wywołuje konwencję z funkcjami składowymi (tak, że osoba oczekująca oczekuje, że "ten" zostanie przekazany w ECX). Zapobiega to działaniu 'conv3', nawet jeśli zawiera poprawny adres kodu. –

+0

Dokładnie to się spodziewam. –

1

Wygląda na to, że na początku implementacji pierwsze size(void*) bajtów instancji funkcji typu wskaźnik-do-członka, typu void (test::*)(), dzieje się tak, jak w tym przypadku, jako adres funkcji w pamięci. Jako szczegół implementacji funkcja ta jest wywoływana, jak gdyby była to funkcja wolna o numerze this jako pierwszym parametrze. Dlatego wydaje się, że działa conv3.

Jednak twoje szczęście skończyło się, gdy próbujesz skopiować te pierwsze bajty sizeof(void*) do innej instancji typu wskaźnik-do-członka funkcji. Niezainicjowane śmieci w repozytorium conv2, po interpretacji jako reszta funkcji wskaźnik-do-członka po początkowym adresie kodu, spowodowały, że coś poszło nie tak. Podejrzewam, że istnieją tam flagi i przesunięcia, aby rejestrować informacje o funkcjach wirtualnych oraz dziedziczeniu wielokrotnym i wirtualnym. Kiedy ta informacja jest nieprawidłowa, coś pójdzie nie tak.

Powiązane problemy