私はvtablesとvpointersの内部動作についてもう少し学ぼうとしていたので、いくつかのトリックを使用してvtableに直接アクセスすることにしました。私は2つのクラスを作成Base
しDerv
、それぞれに2つのvirtual
関数があります(の関数Derv
をオーバーライドしますBase
)。
class Base
{
int x;
int y;
public:
Base(int x_, int y_) : x(x_), y(y_) {}
virtual void foo() { cout << "Base::foo(): x = " << x << '\n'; }
virtual void bar() { cout << "Base::bar(): y = " << y << '\n'; }
};
class Derv: public Base
{
int x;
int y;
public:
Derv(int x_, int y_) : Base(x_, y_), x(x_), y(y_) {}
virtual void foo() { cout << "Derived::foo(): x = " << x << '\n'; }
virtual void bar() { cout << "Derived::bar(): y = " << y << '\n'; }
};
ここで、コンパイラは各クラスにvtableポインタを追加し、メモリの最初の4バイト(32ビット)を占有します。size_t*
ポインタがサイズの別のポインタを指しているため、オブジェクトのアドレスをにキャストしてこのポインタにアクセスしましたsizeof(size_t)
。これで、vpointerにインデックスを付け、その結果を適切なタイプの関数ポインターにキャストすることで、仮想関数にアクセスできます。これらのステップを関数にカプセル化しました。
template <typename T>
void call(T *ptr, size_t num)
{
typedef void (*FunPtr)();
size_t *vptr = *reinterpret_cast<size_t**>(ptr);
FunPtr fun = reinterpret_cast<FunPtr>(vptr[num]);
//setThisPtr(ptr); added later, see below!
fun();
}
メンバー関数の1つがこのように呼び出された場合、たとえばcall(new Base(1, 2), 0)
Base :: foo()を呼び出す場合、ポインターなしで呼び出されるため、何が起こるかを予測することは困難this
です。this
g ++がレジスタに-pointerを格納することを知って、少しテンプレート化された関数を追加することでこれを解決しました(ただし、これにより、コンパイラフラグecx
を使用してコンパイルする必要があります)。-m32
template <typename T>
void setThisPtr(T *ptr)
{
asm ( mov %0, %%ecx;" :: "r" (ptr) );
}
setThisPtr(ptr)
上記のスニペットの行のコメントを解除すると、プログラムとして機能するようになります。
int main()
{
Base* base = new Base(1, 2);
Base* derv = new Derv(3, 4);
call(base, 0); // "Base::foo(): x = 1"
call(base, 1); // "Base::bar(): y = 2"
call(derv, 0); // "Derv::foo(): x = 3"
call(derv, 1); // "Derv::bar(): y = 4"
}
この小さなプログラムを書く過程で、vtablesがどのように機能するかについてより多くの洞察を得て、他の人がこの資料をもう少しよく理解するのに役立つかもしれないので、これを共有することにしました。ただし、まだいくつか質問があります
。1. 64ビットバイナリをコンパイルするときにthis-pointerを格納するために使用されるレジスタ(gcc 4.x)はどれですか。ここに記載されているように、すべての64ビットレジスタを試しました。http://developers.sun.com/solaris/articles/asmregs.html
2. this-pointerはいつ/どのように設定されますか?コンパイラは、オブジェクトを介した各関数呼び出しで、これを行ったのと同じようにthisポインタを設定していると思います。これはポリモーフィズムが実際に機能する方法ですか?(最初にthis-pointerを設定してから、vtableから仮想関数を呼び出しますか?)