指向不同地址的vTables和函数指针

时间:2012-04-02 21:16:42

标签: c++ memory-management vtable

我最近在bitsquid博客上阅读了一篇关于如何管理内存的文章,作者开始讨论vtable以及编译器如何将类添加到类中。以下是article的链接。因为我几乎不知道关于这个问题的事情,所以我开始在网上寻找解释。我来到this link。根据我读到的内容,我制作了以下代码:

char cache[24];

printf("Size of int = %d\n", sizeof(int));
printf("Size of A = %d\n", sizeof(A));

A* a = new(cache)A(0,0);
printf("%s\n",cache);
printf("vTable    : %d\n",*((int*)cache));
printf("cache addr: %d\n",&cache);

int* funcPointer = (int*)(*((int*)cache));
printf("A::sayHello: %d\n",&A::sayHello);
printf("funcPointer: %d\n",*funcPointer);

A是一个包含两个整数成员和一个虚函数sayHello()的类。

编辑:以下是课程定义:

class A {
public:
    int _x;
    int _y;
public:
    A(int x, int y) : _x(x), _y(y){ }
    virtual void sayHello() { printf("Hello You!"); }
};

基本上我试图做的是查看vtable中的指针是否指向与&A::sayHello的地址相同的位置,但问题是当我运行程序时,地址在vtable中的指针内部,sayHello()地址始终具有295的差异。有谁知道为什么会发生这种情况?是否有某种标题被添加,我错过了?我在64位机器上运行visual studio express 2008。

根据我调试的*funcPointer返回的地址是函数sayHello()的真实地址。但为什么&A::sayHello()会返回不同的地址?

2 个答案:

答案 0 :(得分:8)

C ++有一个有趣的特点:

如果您使用指向虚函数的指针并使用它,则将解析并调用虚函数。

我们举一个简单的例子

struct A
{
    virtual DoSomething(){ printf("A"); }
};

struct B: public A
{
    virtual DoSomething() { printf("B"); }
};

void main()
{
    A * a, b;
    void (A::*pointer_to_function)();

    pointer_to_function = &A::DoSomething;
    a = new A;
    b = new B;

    (a.*pointer_to_function)() //print "A"
    (b.*pointer_to_function)() //print "B"
}

因此,您使用&A::DoSomething看到的地址是蹦床的地址,而不是真实的函数地址。

如果你去装配,你会看到该函数做了类似的事情(寄存器可能会改变,但ecx代表这个指针):

mov eax, [ecx] ; Read vtable pointer
lea edx, [eax + 4 * function_index ] ; function_index being the index of function in the vtable
call edx  

答案 1 :(得分:1)

请注意,这是所有实现定义的!

虽然有些实现可能会使用trampoline函数,但这不是唯一的方法,而不是gcc如何实现它

使用gcc,如果你运行你发布的代码,你会得到这个:

A::sayHello: 1

因为代替存储trampoline函数的地址,虚拟函数的成员函数指针存储为{ vtable offset + 1, this-ptr offset },而你打印出来的是第一个单词。 (有关详细信息,请参阅http://sourcery.mentor.com/public/cxx-abi/abi.html#member-pointers)。

在这种情况下,sayHello是唯一的vtable条目,因此vtable偏移量为0. 1被添加以将此成员函数指针标记为虚拟成员函数。

如果在使用g ++编译时检查用于执行成员函数指针调用的程序集,则会在调用站点获得一些指令,如果它是虚拟成员函数指针,则会计算要调用的函数的地址:

        (a->*pointer_to_function)(); //print "A"
Load the first word of the member function pointer into rax:
      4006df:       48 8b 45 c0             mov    -0x40(%rbp),%rax
Check the lower bit:
      4006e3:       83 e0 01                and    $0x1,%eax
      4006e6:       84 c0                   test   %al,%al
If non-virtual skip the next bit:
      4006e8:       74 1b                   je     400705 <main+0x81>
virtual case, load the this pointer offset and add the this pointer (&a):
      4006ea:       48 8b 45 c8             mov    -0x38(%rbp),%rax
      4006ee:       48 03 45 e0             add    -0x20(%rbp),%rax
rax is now the real 'this' ptr. dereference to get the vtable ptr:
      4006f2:       48 8b 10                mov    (%rax),%rdx
Load the vtable offset and subtract the flag:
      4006f5:       48 8b 45 c0             mov    -0x40(%rbp),%rax
      4006f9:       48 83 e8 01             sub    $0x1,%rax
Add the vtable offset to the addr of the first vtable entry (rdx):
      4006fd:       48 01 d0                add    %rdx,%rax
Dereference that vtable entry to get a real function pointer:
      400700:       48 8b 00                mov    (%rax),%rax
Skip the next line:
      400703:       eb 04                   jmp    400709 <main+0x85>

non-virt case, load the function address from the member function pointer:
      400705:       48 8b 45 c0             mov    -0x40(%rbp),%rax

Load the 'this' pointer offset:
      400709:       48 8b 55 c8             mov    -0x38(%rbp),%rdx
Add the actual 'this' pointer:
      40070d:       48 03 55 e0             add    -0x20(%rbp),%rdx
And finally call the function:
      400711:       48 89 d7                mov    %rdx,%rdi
      400714:       ff d0                   callq  *%rax