1

クラスAのvtableの内容、特に仮想デストラクタを見たいのですが、関数ポインタで呼び出すことができません。これが私のコードです:

typedef void (*fun)();


class A {
public:
     virtual func() {printf("A::func() is called\n");}
     virtual ~A() {printf("A::~A() is called\n");}
};

//enter in the vtable 
void *getvtable (void* p, int off){
     return (void*)*((unsigned int*)p+off);
}

//off_obj is used for multiple inherence(so not here), off_vtable is used to specify the   position of function in vtable
fun getfun (A* obj, unsigned int off_obj,int off_vtable){
     void *vptr = getvtable(obj,off_obj);
     unsigned char *p = (unsigned char *)vptr;
     p += sizeof(void*) * off_vtable;
     return (fun)getvtable(p,0);
}


void main() {
     A* ptr_a = new A;
     fun pfunc = getfun(ptr_a,0,0);
     (*pfunc)();
     pfunc = getfun(ptr_a,0,1);
     (*pfunc)(); //error occurred here, this is supposed to be the virtual desctrutor, why?
}
4

2 に答える 2

1

議論のために、問題の vtable が実際には通常のメモリ アドレスのテーブルとして、あなたが思っているように配置されており、それらのアドレスを関数ポインタにキャストすると、呼び出し可能であると仮定しましょう。

少なくとも 2 つの問題があります。

  1. メンバー関数の呼び出し規則は、通常の関数と必ずしも同じではありません。Microsoft のデフォルトの呼び出し規約はthiscallです。これは、メソッドが ECX レジスタで呼び出されているオブジェクトへのポインターを配置します。それを手動で指定する機能はありません。これを実現する唯一の方法は、メンバー関数が呼び出される方法でメンバー関数をobj.f()呼び出すことです。これには、 orのような構文が含まれますpobj->f()。低レベルの詳細をすべて正しく取得するためにマシン コードまたはアセンブラーを作成しない限り、関数へのポインター (メンバー関数ポインターでさえも) でこれを行うことはできません。

    (直接または他のメンバーへの暗黙的な参照によって)funcを参照しないため、この問題に遭遇することはありません。thisただし、デストラクタはそうします。デストラクタは特殊であり、vtable に実際に格納されるのは、実際のデストラクタを呼び出し、隠しパラメータとして渡されたいくつかのフラグをチェックして、オブジェクトのメモリを解放する必要があるかどうかを判断する、コンパイラによって生成されたヘルパー関数へのポインタです。たまたま ECX にある値は呼び出しには関係ありませんが、func呼び出しに適していることが非常に重要~Aです。

  2. デストラクタは通常の関数とは異なります。上で述べたように、コンパイラは 1 つ以上のヘルパー関数を生成でき、this. コードでそれを説明していません。コンパイラは、配列デストラクタと非配列デストラクタに対して個別のヘルパーを生成するため、現時点では、vtable のインデックス 1 でどちらを見つけたかさえわかりません。しかし、有効なフラグ パラメータを渡さなかったので、値を渡す方法がthisないため、vtable で何を見つけるかは問題ではありません。

stdcallなどの別の呼び出し規約を指定することで、最初の問題を解決することができます。これにより、thisパラメーターが残りのパラメーターと共にスタックに戻され、関数ポインターを呼び出すときにパラメーターを渡すことができます。の場合funcfun次のような宣言が必要になります。

typedef void (__stdcall * fun)(A*);

次のように呼び出しpfuncます。

pfunc(ptr_a);

2 番目の問題を解決するには、正しいデストラクタ ヘルパーを見つけることができるように、vtable 関数の実際の順序を決定する必要があります。それを呼び出すには、別の関数ポインタ宣言も必要です。デストラクタには技術的に戻り値の型はありませんが、void十分に機能します。次のようなものを使用できます。

typedef void (__stdcall * destr)(A*, unsigned flags);

この回答のほとんどで、プログラムを逆コンパイルして C++ に戻す目的で、プログラム内の特定のパターンを認識することに関するIgorsk の記事を使用しました。パート 2 ではクラスについて説明します。

于 2012-05-18T14:51:31.520 に答える
-1

デストラクタを呼び出しません。operator delete() を呼び出すと、デストラクタが特定されます。デストラクタを直接呼び出すことは、未定義の動作です。NULL を逆参照するのと同じ意味で、つまり、私が見たすべてのプラットフォームで爆発します。

于 2012-05-18T08:44:37.900 に答える