Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Double indirection in C++ vtables

I wrote this very simple C++ program, and I was wondering about why the compiler lays out the vtable across two pointer dereferences. Here's the C++ program:

class Foo {
 public:
  virtual void bar() {
  }
};

int main(int argc, char *arv[]) {
  Foo foo;
  Foo *foo_p(&foo);
  foo_p->bar();
}

Now, I can look at the compiler-generated assembly:

$ g++ -ggdb -Wall -O0 -S test.cpp

Here are the relevant sections:

    .loc 1 9 0 
    leaq    -16(%rbp), %rax  # put the address of 'foo' in %rax
    movq    %rax, %rdi       # use it as the first argument of the following function
    call    _ZN3FooC1Ev      # call the Foo constructor
    .loc 1 10 0
    leaq    -16(%rbp), %rax  # put the address of 'foo' in %rax
    movq    %rax, -24(%rbp)  # create 'foo_p' on the stack
    .loc 1 11 0
    movq    -24(%rbp), %rax  # load 'foo_p' into %rax
    movq    (%rax), %rax     # dereference the pointer, put it in %rax
                             # %rax now holds the hidden pointer in 'foo', which is the vtable pointer
    movq    (%rax), %rdx     # dereference the pointer ::again:: (with an offset of 0), put it in %rdx
                             # %rdx now holds a function pointer from the vtable
    movq    -24(%rbp), %rax  # create the 'this' pointer (== foo_p) and put it in %rax
    movq    %rax, %rdi       # use the 'this' pointer as the first argument to the following function
    call    *%rdx            # call Foo::bar (via the vtable)

Why the second pointer dereference is necessary? Why doesn't the 'hidden' vtable pointer in the object point directly into the vtable?

edit: It ::is:: pointing directly to the vtable. I just got confused with my pointers :-P

like image 990
Litherum Avatar asked Feb 17 '23 19:02

Litherum


2 Answers

movq    -24(%rbp), %rax  # load 'foo_p' into %rax
movq    (%rax), %rax     # fetch VTABLE
movq    (%rax), %rdx     # fetch function `bar` from VTABLE. 

You'd see it better if you added a baz (or kerflunk) function as a second function to your class, you'd see the second fetch be at 8 into the VTABLE.

You can see the structure inside the class as something like this (note this is "for illustration purposes, not intended to be reality")

struct VTABLE
{
    void (*bar)();
};

struct Foo
{
    VTABLE *vtable;
};

Inside the constructor for Foo [which exists even though you haven't declared one], there is a piece of code that does:

this->vtable = &VTABLE_Foo; 

and somewhere the compiler has done (again, for illustration purposes, the names are certainly different):

VTABLE VTABLE_Foo = { foo::bar };

So to call bar, we would do:

foo_p->vtable->bar(foo_p);

What the compiler shows is:

void (*temp)() = foo_p->vtable->bar;
temp(foo_p);

This is almost certainly a consequence of using -O0, and if you compile with optimization, the compiler will do it more straight forward (including possibly realizing it doesn't need a vtable in this case and inline the function which doesn't do anything, thus eliminate the call completely).

like image 90
Mats Petersson Avatar answered Feb 23 '23 19:02

Mats Petersson


The first puts the vtable pointer into %rax, and the second puts the function pointer into %rdx.

like image 21
Some programmer dude Avatar answered Feb 23 '23 18:02

Some programmer dude