-1

For an exercise I need to call a virtual method by way of the virtual method table (vtable). I'm experiencing some behavior I can't understand--the correct method is called, but with the wrong value for this. Here's some code that first demonstrates the correct behavior by fetching the method pointer directly, then demonstrates the wonky behavior by using the vtable:

#include <cstdio>

class Thing
{
public:
  virtual void foo()
  {
    std::printf("this:\t\t%p (in foo())\n", this);
  }
};

typedef void (Thing::*METHOD_PTR)();

void print_method_ptr(METHOD_PTR ptr) {
  // Helper function to display method pointers.
  for (size_t i = 0; i < sizeof ptr; ++i)
    printf("%d ", reinterpret_cast<char *>(&ptr)[i]);
}

void direct(Thing* thing_ptr) {
  std::printf("In direct().\n");

  // Get the pointer directly.
  METHOD_PTR foo_ptr = &Thing::foo;

  std::printf("&Thing::foo: ");
  print_method_ptr(&Thing::foo);
  std::printf("\nfoo_ptr: ");
  print_method_ptr(foo_ptr);
  if (foo_ptr == &Thing::foo) {
    std::printf("\nfoo_ptr == &Thing::foo\n");
  } else {
    std::printf("\nfoo_ptr != &Thing::foo\n");
  }

  std::printf("thing_ptr:\t%p\n", thing_ptr);
  (thing_ptr->*foo_ptr)();
}

void via_vtable(Thing* thing_ptr) {
  std::printf("In via_vtable().\n");

  // Retrieve foo_ptr from the vtable.
  METHOD_PTR** thing_ptr_cast = reinterpret_cast <METHOD_PTR**> (thing_ptr);
  METHOD_PTR* vtable_ptr = *thing_ptr_cast;
  METHOD_PTR foo_ptr = vtable_ptr[0];

  std::printf("&Thing::foo: ");
  print_method_ptr(&Thing::foo);
  std::printf("\nfoo_ptr: ");
  print_method_ptr(foo_ptr);
  if (foo_ptr == &Thing::foo) {
    std::printf("\nfoo_ptr == &Thing::foo\n");
  } else {
    std::printf("\nfoo_ptr != &Thing::foo\n");
  }

  std::printf("thing_ptr:\t%p\n", thing_ptr);
  (thing_ptr->*foo_ptr)();
}

int main()
{
  Thing thing;

  direct(&thing);
  std::printf("\n");
  via_vtable(&thing);

  return 0;
}

The output:

In direct().
&Thing::foo: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
foo_ptr: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
foo_ptr == &Thing::foo
thing_ptr:  0x7fffc6054940
this:       0x7fffc6054940 (in foo())

In via_vtable().
&Thing::foo: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
foo_ptr: -42 9 64 0 0 0 0 0 53 84 104 105 110 103 0 0 
foo_ptr != &Thing::foo
thing_ptr:  0x7fffc6054940
this:       0xe76e2f6d9d75 (in foo())

This is very confusing. My main questions:

  • In via_vtable(), why does foo_ptr != &Thing::foo?
  • Why does foo() still get called even though the method pointer is apparently incorrect?
  • When foo() is called via the vtable, why is the value for this incorrect?
  • Is there a better way to retrieve the method from the vtable?

For the exercise I've been directed to the Wikipedia page for Virtual method tables, which explains their memory layout for g++.

I'm using g++ 4.8.4 on 64-bit Linux.

EDIT: Using -fdump-class-hierarchy with g++ suggests that the Wikipedia page is wrong about the vtable memory layout:

Vtable for Thing
Thing::_ZTV5Thing: 3u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI5Thing)
16    (int (*)(...))Thing::foo

However, that doesn't explain how foo() was actually executed before (and it certainly doesn't explain the wrong this in foo().) Also, if I change METHOD_PTR foo_ptr = vtable_ptr[0]; to METHOD_PTR foo_ptr = vtable_ptr[1]; (which should then be offset 16, since sizeof(METHOD_PTR) is 16), I get a segmentation fault when I call the method.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

2 Answers2

0

I think this could help. Why do function pointer definitions work with any number of ampersands '&' or asterisks '*'?

I have visual studio and in my case is this always same. sizeof(METHOD_PTR) should be 4. It is only pointer.

Community
  • 1
  • 1
elanius
  • 487
  • 2
  • 19
0

Figured out how to call the virtual method. Note that I've changed the typedef and the line that actually calls the method.

#include <cstdio>

class Thing
{
public:
  virtual void foo()
  {
    std::printf("this:\t\t%p (in foo())\n", this);
  }
};

typedef void (*METHOD_PTR)(Thing*);

void via_vtable(Thing* thing_ptr) {
  // Retrieve foo_ptr from the vtable.
  METHOD_PTR** thing_ptr_cast = reinterpret_cast <METHOD_PTR**> (thing_ptr);
  METHOD_PTR* vtable_ptr = *thing_ptr_cast;
  METHOD_PTR foo_ptr = vtable_ptr[0];

  std::printf("thing_ptr:\t%p\n", thing_ptr);
  (*foo_ptr)(thing_ptr);
}

int main()
{
  Thing thing;
  via_vtable(&thing);
  return 0;
}

This gives me the following output:

thing_ptr:  0x7fff5f739da0
this:       0x7fff5f739da0 (in foo())

I still don't understand why the entry in the vtable is a different type (and value) from &Thing::foo, nor why the vtable given by -fdump-class-hierarchy has different offsets than the runtime vtable. If the vtable provided a bound method (like a Python bound method, with self already determined), then it would make sense, but seeing as I still have to pass in a pointer to a Thing instance.... hrrm.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459