I was recently reading an article in the bitsquid blog about how to manage memory and the author began to talk about the vtable and how there is a pointer added to the class by the compiler. Here is the link for the article. So because I hardly knew anything about the vtalbe I began to search the web for an explanation. I came accross this link. Based on what I read I made the following code:
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
is a class with two integers members and a virtual function sayHello()
.
EDIT : Here is the class definition:
class A {
public:
int _x;
int _y;
public:
A(int x, int y) : _x(x), _y(y){ }
virtual void sayHello() { printf("Hello You!"); }
};
Basically what I was trying to do is see if the pointer inside the vtable would point to the same place as where the address I get from &A::sayHello
, but the thing is that when I run the program, the address inside the pointer in the vtable and the sayHello()
address always has a difference of 295
. Does anyone know why this could be happening? Is there some kind of header that's added that I'm missing? I'm running visual studio express 2008 on a 64 bit machine.
From what I have debugged the address that is returned by *funcPointer
is the true address of the function sayHello()
. But why is the &A::sayHello()
returning a different address?
C++ has an interesting feature :
If you take a pointer to a virtual function and use it, the virtual function will be resolved and called.
Let's take a simple example
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"
}
So, the address you see using &A::DoSomething
is the address of the trampoline, not the real function address.
If you go to assembly, you'll see that function does something like that (register may change but ecx which represent the this pointer ):
mov eax, [ecx] ; Read vtable pointer
lea edx, [eax + 4 * function_index ] ; function_index being the index of function in the vtable
call edx
Note that this is all implementation defined!
Whilst some implementations may use a trampoline function, this is not the only way to do it, and not how gcc implements it
With gcc, if you run the code you posted, you'll get this:
A::sayHello: 1
Because instead of storing the address of a trampoline function, the member function pointer for a virtual function is stored as { vtable offset + 1, this-ptr offset }
, and what you're printing out is the first word of that. (see http://sourcery.mentor.com/public/cxx-abi/abi.html#member-pointers) for more details).
In this case, sayHello
is the only vtable entry, and so the vtable offset is 0. 1
is added to flag this member function pointer as being a virtual member function.
If you inspect the assembley for doing the member function pointer calls when compiled with g++, you'll get some instructions at the call site that calculate the address of the function to be called if it's a virtual member function pointer:
(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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With