我想了解更多关于vtables和vpointers的内部工作原理,所以我决定尝试直接使用一些技巧来访问vtable。我创建了两个类Base
和Derv
,每个类都有两个virtual
函数(Derv
覆盖了Base
的函数)。vtables和这个指针
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位)。由于指针指向另一个大小为sizeof(size_t)
的指针,我通过将对象的地址转换为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();
}
当memberfunctions之一被称为这种方式,例如call(new Base(1, 2), 0)
来调用Base :: foo(),很难预测会发生什么,因为它们被称为没有this
指针。
template <typename T>
void setThisPtr(T *ptr)
{
asm (mov %0, %%ecx;" :: "r" (ptr));
}
取消注释setThisPtr(ptr)
线的片段:我通过在ecx
寄存器加入少许的模板化功能,知道G ++存储this
终场前(然而,这迫使我与-m32
编译器标志编译)解决了这个以上,使得现在有一个工作程序:
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"
}
我决定分享这一点,因为在写这个小程序的过程中,我获得了更多的了解虚函数表是如何工作的,它可能会帮助别人理解这种材料好一点。 但是我仍然有一些问题:
1.编译64位二进制文件时使用了哪个寄存器(gcc 4.x)来存储该指针?我尝试了所有64位寄存器,如下所示:http://developers.sun.com/solaris/articles/asmregs.html
2.何时/如何设置此指针?我怀疑编译器通过一个对象以类似的方式在每个函数调用中设置了这个指针,就像我刚刚做的那样。这是多态性实际工作的方式吗? (通过先设置这个指针,然后从vtable调用虚函数?)。
这对于典型的SO问题有点长。你能把这个问题凝聚到核心问题上吗? (实际上,你的任何帖子是否与最后的问题直接相关?) –
@奥利查尔斯沃斯那么这对我来说,因为这就是我对这些问题的看法。第二个问题询问编译器是否使用类似的方法来启用多态,所以我认为我应该包括我自己的。你会建议另一种媒介,我可以通过它分享这些信息并提出问题吗? – JorenHeit
你应该在这里提出问题,只要有足够的背景让他们理解(这听起来不像你需要*任何*这些特定问题的背景)。如果你想分享你发现的东西,你应该建立一个博客... –