I'm calling virtual functions from the address in the virtual table as an exercise to test my understanding of the concept. However, as soon as I thought I made a breakthrough in my understanding of the virtual method table, I run into another issue that I just don't understand.

In the code below, I've created a class called Car which contains a member variable x and two virtual functions, first and second. Now, I call these two virtual methods by hacking through the virtual table. The first function returns the correct answer, but the second returns some random value or garbage instead of what it was initialized to be.

#include <cstdio>

class Car
{
private:
    int x;

    virtual int first()
    {
        printf("IT WORKS!!\n");
        int num = 5;
        return num;
    }
    virtual int second()
    {
        printf("IT WORKS 2!!\n");
        //int num  = 5;
        return x;
    }


public:

    Car(){
        x = 2;
    }
};

int main()
{
    Car car;
    void* carPtr = &car;
    long **mVtable =(long **)(carPtr);

    printf("VTable: %p\n", *mVtable);
    printf("First Entry of VTable: %p\n", (void*) mVtable[0][0]);
    printf("Second Entry of VTable: %p\n", (void*) mVtable[0][1]);

    if(sizeof(void*) == 8){
        printf("64 bit\n");
    }

    int (*firstfunc)() = (int (*)()) mVtable[0][0];
    int x = firstfunc();    

    int (*secondfunc)() = (int (*)()) mVtable[0][1];
    int x2 = secondfunc();

    printf("first: %d\nsecond: %d", x, x2);
    return 0;
}

If someone can point me to what I'm doing wrong that would be appreciated. Also, since this works differently across compilers, I'm testing it on http://cpp.sh/ using c++14.

That code out outputs, where the "garbage" second output is subject to change:

VTable: 0x400890
First Entry of VTable: 0x400740
Second Entry of VTable: 0x400720
64 bit
IT WORKS!!
IT WORKS 2!!
first: 5
second: -888586240 
3

There are 3 best solutions below

3
On BEST ANSWER

Methods are functions, but method pointers are generally not function pointers.

The calling convention of calling methods does not always agree with the calling convention of calling functions.

We can get around this. With yet more undefined behavior, but that works at least sometimes.

MSVC clang g++

Code:

template<class Sig>
struct fake_it;

template<class R, class...Args>
struct fake_it<R(Args...)>{
    R method(Args...);

    using mptr = decltype(&fake_it::method);
};
template<class R, class...Args>
struct fake_it<R(Args...) const> {
    R method(Args...) const;

    using mptr = decltype(&fake_it::method);
};

template<class Sig>
using method_ptr = typename fake_it<Sig>::mptr;

template<class Sig>
struct this_helper {
    using type=fake_it<Sig>*;
};
template<class Sig>
struct this_helper<Sig const>{
    using type=fake_it<Sig> const*;
};

template<class Sig>
using this_ptr = typename this_helper<Sig>::type;

now this test code:

Car car;
void* carPtr = &car;
auto **mVtable = (uintptr_t **)(carPtr);
printf("VTable: %p\n", *mVtable);
printf("First Entry of VTable: %p\n", (void*)mVtable[0][0]);
printf("Second Entry of VTable: %p\n", (void*)mVtable[0][1]);

if(sizeof(void*) == 8){
    printf("64 bit\n");
}

auto firstfunc = to_method_ptr<int()>(mVtable[0][0]);
int x = (this_ptr<int()>(carPtr)->*firstfunc)();    

auto secondfunc = to_method_ptr<int()>(mVtable[0][1]);
int x2 = (this_ptr<int()>(carPtr)->*secondfunc)();

printf("first: %d\nsecond: %d", x, x2);

The code above relies on method pointers being a pair of function pointer and a second section that if all 0s is non-virtual dispatch, and the vtable to contain just the function pointer component.

So we can reconstruct a method pointer from the data in the vtable by padding a buffer with 0s, then interpreting the memory as a method pointer.

To get the call to work, we create a fake type with a method that matches our signature, then cast our pointer to that type and invoke it with a member function pointer reconstructed from our original type's vtable.

This, we hope, mimics the calling convention of that the compiler uses for other method calls.


In clang/g++ non-virtual method pointers are two pointers with the second one ignored. Virtual method pointers, I believe, use the second pointer-sized data.

In MSVC, non-virtual method pointers are the size of one pointer. Virtual method pointers with a virtual inheritance tree are not the size of one pointer. I believe this violates the standard (that requires that member pointers be inter-castable between).

In both cases, the vtable appears to store the first half of each non-virtual method pointer.

5
On

The constructor, which sets x = 2, doesn't run when you call a function pointer directly into the vtable. You're returning uninitialized memory from second, which can be anything.

2
On

Methods are indeed generally implemented as regular functions, but they need to receive the this pointer to access the data of a specific instance - in facts, when you invoke a method over an instance a pointer to the instance gets passed as a hidden parameter.

In your code you aren't passing it in, so the method just returns garbage - it's probably using whatever happens to be in a register or on the stack as if it was the instance pointer; you are lucky enough that it doesn't plainly crash.

You may try changing your prototypes to accept a Car* parameter and pass &car to it, but it may or may not work, depending on the calling convention used by your compiler/platform:

  • on Win32/x86/VC++, for example, methods use the stdcall calling convention (or cdecl for variadics), but receive the this pointer in ecx, something you cannot emulate through a regular function call;
  • on the other hand, x86 gcc just handles them as cdecl functions, passing this implicitly as if it was the last parameter.