From Schmid.wiki
Jump to: navigation, search

Virtual Function Table

I assume the following definitions (remember that a struct is defined as a class with default public: access):

struct Base {                             
    Base() : x(4) {}                      
    virtual int get_value() { return x; }  
    int x;                                
};                                        
struct Derived : public Base {            
    int get_value() { return  2 * x; }     
};                   
...
Base b;
Derived d;                            
Base *dp = &d; // pointer (upcasted)
Base &dr = d;  // reference (upcasted)                    

Each class with virtual member functions has a static virtual function table (Vtable) containing pointers to each virtual member function for that class. Each object has a Vtable pointer (vptr), which points to the Vtable of its class after initialization

To inspect the Vtables with g++, you can use:

g++ -fdump-class-hierarchy program.cpp

This creates a new file program.cpp.class:

Vtable for Base
Base::_ZTV4Base: 3u entries
0     0u                            // ? (some sort of offset)
4     (int (*)(...))(&_ZTI4Base)    // Base typeinfo
8     Base::get_value

Class Base
   size=8 align=4
   base size=8 base align=4
Base (0xb7d20800) 0
    vptr=((&Base::_ZTV4Base) + 8u)  // vptr = address of first function
                                    //        in Vtable

Vtable for Derived
Derived::_ZTV7Derived: 3u entries
0     0u                            // ? (some sort of offset)
4     (int (*)(...))(&_ZTI7Derived) // Derived typeinfo
8     Derived::get_value

Class Derived
   size=8 align=4
   base size=8 base align=4
Derived (0xb7d20e00) 0
    vptr=((&Derived::_ZTV7Derived) + 8u) // vptr = address of first
  Base (0xb7d20e40) 0                    //        function in Vtable
      primary-for Derived (0xb7d20e00)

This is the Vtable generated for Base by g++, in assembler code:

        .weak   _ZTV4Base                                      
        .section        .gnu.linkonce.r._ZTV4Base,"a",@progbits
        .align 8
        .type   _ZTV4Base, @object                             
        .size   _ZTV4Base, 12                                  
_ZTV4Base:                            ; class Base Vtable                                   
        .long   0                     ; ? (some sort of offset)
        .long   _ZTI4Base             ; Base typeinfo (defined elsewhere)
        .long   _ZN4Base9get_valueEv  ; Base::get_value

And the Vtable generated for Derived:

        .weak   _ZTV7Derived
        .section        .gnu.linkonce.r._ZTV7Derived,"a",@progbits
        .align 8                                                  
        .type   _ZTV7Derived, @object
        .size   _ZTV7Derived, 12                                  
_ZTV7Derived:                           ; class Derived VTable
        .long   0                       ; ? (some sort of offset)
        .long   _ZTI7Derived            ; Derived typeinfo (defined elsewhere)
        .long   _ZN7Derived9get_valueEv ; Derived::get_value

The Derived constructor:

        .section        .gnu.linkonce.t._ZN7DerivedC1Ev,"ax",@progbits
        .align 2
        .weak   _ZN7DerivedC1Ev                                       
        .type   _ZN7DerivedC1Ev, @function                            
_ZN7DerivedC1Ev:
        pushl   %ebp                    ; save caller base pointer
        movl    %esp, %ebp              ; define callee base pointer
                                        ; push 'this' to stack:
        subl    $4, %esp                ;   allocate 1 word on stack for 'this'
        movl    8(%ebp), %eax           ;   copy 'this' to eax
        movl    %eax, (%esp)            ;   ... and from there to top of stack
        call    _ZN4BaseC2Ev            ; call Base::Base (non-static member
                                        ;   functions always use 'this' as an
                                        ;   implicit first argument)
        movl    8(%ebp), %eax           ; copy 'this' to eax
        movl    $_ZTV7Derived+8, (%eax) ; copy Derived Vtable to 'this'
                                        ;   i.e. store Vtable in first attribute
                                        ;   of 'this' (vptr). Note that the
                                        ;   vptr points to the first function,
                                        ;   Derived::get_value
        leave                           ; restore caller stack frame
        ret                             ; return to caller
        .size   _ZN7DerivedC1Ev, .-_ZN7DerivedC1Ev

Invocation

Non-virtual method call d.get_value():

        leal    -16(%ebp), %eax         ; copy pointer to 'd' in eax
        movl    %eax, (%esp)            ; ... and from there to top of stack
        call    _ZN7Derived9get_valueEv ; call Derived::get_value() with
                                        ;   pointer to 'd' as implicit first
                                        ;   argument

Virtual method call dr.get_value():

        movl    -20(%ebp), %eax         ; store pointer to 'd' in eax
        movl    (%eax), %edx            ; copy vptr (first element of 'd') to
                                        ;   edx
        movl    -20(%ebp), %eax         ; store pointer to 'd' in eax (again)
        movl    %eax, (%esp)            ; ... and from there to top of stack
        movl    (%edx), %eax            ; copy first element in Vtable
                                        ;   (Derived::get_value) to eax
        call    *%eax                   ; call Derived::get_value

Performance

Obviously, there is a small performance issue with virtual methods. If you do high-performance applications, note that this example performs perfectly in the time domain (although not in the space domain):

struct Thing {
    virtual ~Thing() {}
    virtual int method() = 0;
};
struct Thing2 : public Thing {
    int method() { return 10; } // note: not virtual
};
int main() {
    Thing2 thing;
    return thing.method();
}

The 'main' code, when optimized, resolves to:

main:
        pushl   %ebp
        movl    $10, %eax
        movl    %esp, %ebp
        popl    %ebp
        ret

Which is equivalent to:

int main() {
    return 10;
}

If we did:

int main() {
    Thing2 thing2;
    Thing & thing = thing2;
    return thing.method();
}

We would have the usual virtual method overhead.