2012-04-02 41 views
4

我最近读有关如何管理内存bitsquid博客的文章,笔者函数指针开始谈论虚函数表,并有如何添加到类的指针由编译器。这里是article的链接。所以,因为我几乎不知道关于vtbebe的任何事情,所以我开始在网上搜索一个解释。我来到了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!"); } 
}; 

基本上我试图做的是看是否为所在地址,我从&A::sayHello得到虚函数表里面的指针会指向同一个地方,但是当我运行该程序时,vtable中指针内的地址和sayHello()地址的地址总是有差别的是295。有谁知道为什么会发生这种情况?是否有某种标题添加,我错过了?我在64位机器上运行visual studio express 2008。

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

+1

你可以显示'A'类的定义吗? – Mysticial 2012-04-02 21:19:28

+0

这是实现定义的,并且更大程度上取决于类的定义。没有'A'(和编译器/体系结构)的定义,几乎没有什么可以说的。 – 2012-04-02 21:26:36

回答

7

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代表this指针):

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

非常感谢,这解释了很多!那么这个蹦床总是出现在课堂上的每一个功能,还是仅仅是虚拟功能? – Kunashu 2012-04-02 21:50:02

+0

不,只是虚拟函数,因为非虚拟的是在编译时确定的。 – crazyjul 2012-04-02 22:21:13

+0

哈哈,用蹦床代替了thunk,太正式了!作为一个附注,thunks(“trampolines”)只有在你明确采用成员函数的地址时才会生成,它是一种优化形式。如果您不接受地址,您甚至不会生成该功能。 – 2012-04-03 00:04:35

1

注意,这是定义的所有执行!

尽管一些实现可能使用蹦床功能,这是不是这样做的唯一途径,而不是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