2012-06-23 41 views
3

我想了解更多关于vtables和vpointers的内部工作原理,所以我决定尝试直接使用一些技巧来访问vtable。我创建了两个类BaseDerv,每个类都有两个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调用虚函数?)。

+0

这对于典型的SO问题有点长。你能把这个问题凝聚到核心问题上吗? (实际上,你的任何帖子是否与最后的问题直接相关?) –

+0

@奥利查尔斯沃斯那么这对我来说,因为这就是我对这些问题的看法。第二个问题询问编译器是否使用类似的方法来启用多态,所以我认为我应该包括我自己的。你会建议另一种媒介,我可以通过它分享这些信息并提出问题吗? – JorenHeit

+0

你应该在这里提出问题,只要有足够的背景让他们理解(这听起来不像你需要*任何*这些特定问题的背景)。如果你想分享你发现的东西,你应该建立一个博客... –

回答

4

在Linux x86_64上,我相信其他类UNIX操作系统,函数调用遵循System V ABI (AMD64),它本身遵循用于C++的IA-64 C++ ABI。根据方法的类型,this指针可以通过第一个参数或第二个参数隐式传递(当返回值具有非平凡的复制构造函数或析构函数时,它必须作为临时栈存在,而第一个参数隐含地是一个指向该空间的指针);否则,虚拟方法调用相同的功能用C调用(在%rdi%rsi%rdx%rcx%r8%r9,溢出到堆栈整数/指针参数;整数/指针返回在%rax;漂浮在%xmm0 - %xmm7;等等) 。虚拟方法调度的工作原理是在vtable中查找一个指针,然后像调用非虚方法一样调用它。我不太了解Windows x64约定,但我相信它的相似之处在于C++方法调用遵循与C函数调用完全相同的结构(它使用与Linux不同的寄存器),只是隐含了一个this争论第一。

+0

对于微软兼容的C++在Don Box的Essential COM的介绍部分解释了vtable的实现(我听说过另一本书Inside COM) –