【问题标题】:vTables and function pointers pointing to different addresses指向不同地址的 vTables 和函数指针
【发布时间】:2012-04-03 07:15:46
【问题描述】:

我最近在bitsquid博客上读了一篇关于如何管理内存的文章,作者开始谈论vtable以及编译器如何将指针添加到类中。这是article 的链接。所以因为我对 vtalbe 几乎一无所知,所以我开始在网上搜索解释。我遇到了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() 返回一个不同的地址呢?

【问题讨论】:

  • 你能说明A类的定义吗?
  • 这是实现定义的,并且在更大程度上取决于类的定义。如果没有A(和编译器/架构)的定义,就没有什么可以说的了。

标签: c++ memory-management vtable


【解决方案1】:

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 看到的地址是蹦床的地址,而不是真正的函数地址。

如果你去汇编,你会看到这个函数做了类似的事情(寄存器可能会改变,但代表 this 指针的 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  

【讨论】:

  • 非常感谢,这解释了很多!那么这个蹦床是总是存在于类中的每个函数中,还是只存在于虚函数中?
  • 不,只针对虚函数,因为非虚函数是在编译时确定的。
  • 哈哈,用蹦床代替thunk,好正式!作为旁注,thunk(“蹦床”)在您显式获取成员函数的地址时生成,这是一种优化形式。如果你不取地址,你甚至不会生成那个函数。
  • 如果有人关心这里是一个解释 thunk("tampolines") 如何工作的链接。 C Tutorial PointertoMember Function 再次感谢所有帮助。
【解决方案2】:

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

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

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

A::sayHello: 1

因为不是存储蹦床函数的地址,而是将虚函数的成员函数指针存储为{ 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

【讨论】:

    猜你喜欢
    • 2018-12-23
    • 1970-01-01
    • 2019-09-07
    • 2020-12-30
    • 2021-09-20
    • 2012-03-22
    相关资源
    最近更新 更多