我最近在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()
会返回不同的地址?
答案 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