Virtual Function


Let's take an example of a simple class with a virtual function
class myClassBase
{
public:
    int baseMember;
    virtual int getVal(int x) { return baseMember + x; }
};

The equivalent C code:
struct myClassBase
{
    void *vtable;
    int baseMember;
};
int myClassBase_getVal(struct myClassBase *this, int x)
{
    return this->baseMember + x;
}
struct
{
    long abiVerInfo;
    char *typeName;
}
myClassBase_typeInfo =
{
    0, // ABI related stuff.
    "myClassBase"
};
struct
{
    void *typeInfo;
    int (*fun1)(struct myClassBase *, int);
}
myClassBase_vtable =
{
    &myClassBase_typeInfo,
    myClassBase_getVal
};
void myClassBase_ctor(struct myClassBase *this)
{
    this->vtable = (void*)&myClassBase_vtable + sizeof(void*);
}

If we create an object of myClassBase, then a diagram describing the above will be as-


Points to understand:
Format of vtable
The vtable contains more than the function pointers. This is like -
Info X
Info Y
.....
Vfun1
Vfun2
....

In general, the vtable can contain one or more info with function pointer table. One of them is typeifo pointer. The object points into the vtable at the offset from where the function pointer starts. This model simplifies the access of virtual functions from the object. If we need to access to other info that is contained in vtable, then we go to the memory location pointed by vtable of object then move the pointer backward.


Format of typeInfo:

Like vtable, the typeinfo contains more fields than a pointer to type name string. These fields are used while casting of objects and runtime type identification. But we will ignore those fields here for simplicity. The type name used in C code and in the diagram is not mangled, but in the actual generated code, this is mangled. Again this is done for readability.


If we call the virtual function, the generated code will be -
struct myClassBase *mycs = operator_new(sizeof(struct myClassBase));
myClassBase_ctor(mycs);
typedef int (*memFunPtr)(struct myClassBase *, int);
memFunPtr fptr= mycs->vtable;
fptr(mycs, 10);

Here, unlike the non-virtual function, the method binding happens at run time. The actual method that is invoked, is decided at runtime by looking into the vtable. In the case of non-virtual function, the compiler generates the code to invoke the method directly without looking into any function pointer table. The compiler has info of which method to call at compile time and generates the code for the invocation of that particular function.
The vtable defines the function pointer in the sequence defined in the C++ code. Now you can guess why the C++ code has to be recompiled if the library code changes the order of declaration of virtual functions.


up

Want to learn (C/C++ internals) directly from the author using video call? Learn more

Do you collaborate using whiteboard? Please try Lekh Board - An Intelligent Collaborate Whiteboard App