5

一部のメンバー関数ポインターをポインターに変換する必要がありvoid*ます (それらを Lua スタックにプッシュする必要があるためですが、問題は Lua 関連ではありません)。

を使用してこれを行いますunion。しかし、メンバー関数ポインターを a に変換してからvoid*、クラスのインスタンスでポインターを呼び出そうとすると、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

次の 2 行をコードに追加すると、答えが明確になります。

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();
};

を呼び出す場合Foo、ポインタは を指している必要がありDerivedます。ただし、 を呼び出すときは、ポインタが!を指している必要があります。したがって、メンバー関数へのポインターには、関数のアドレスと、関数がポインターとして期待するクラスの正しい型のインスタンスを指すようにポインターを修正するための「アジャスター」の両方を含める必要があります。thisBase1::xBarDerivedthisBase2::jthisthis

アジャスターが失われ、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 に答える