Tuesday, August 7, 2007

Comparison of C++ and Java (VIII): Vtable vs. Method Invocation Table and Binary Compatibility (1)

Both Java and C++ have mechanisms to support dynamic polymorphism via run-time method binding, C++ uses vtable to achieve this goal while Java is by method invocation table. The difference of these two mechanisms leads to the different behaviours regarding to binary compatibility.

A vtable exmple

in C++, a vtable contains the addresses of the object's dynamically bound methods. Method calls are performed by fetching the method's address from the object's vtable. The vtable is the same for all objects belonging to the same class, and is therefore typically shared between them.

Considering the following example. Class A defines one virtual function methodA which just prints out a silly message:


A.h

class A{

public:
A(){}
virtual void methodB();
};

A.cc

void A::methodA(){
cout<<"print from method A "<<endl;
};

The vtable of A looks like:

vtable layout of class A:

A::_ZTV1A: 3u entries
0 0u
8 (int (*)(...))(&_ZTI1A)
16 A::methodA

The offset of A::methodA is 16.

Here is a test class to reference class A:

test.cc

int main (int argc, char *argv[]){
A* a = new A();
a->methodA();
}


We compile and link program by the following three steps:

>>g++ -c A.cc
>>g++ -c test.cc
>>g++ A.o test.o –o test

and run test program
>>./test

and get output:
>>Print from method A

In the example above, method call a->methodA fetches methodA from a address with offset 16.

Adding a virtual method breaks binary compatibility in C++

To show this, we add a virtual function methodB to class A right BEFORE methodA :

A.h

class A{
public:
A(){}
virtual void methodB(); // a new added function
virtual void methodA();
};

A.cc

void A::methodA(){
cout<<"print from method A "<<endl;
}

void A::methodB(){
cout<<"print from method B "<<endl;
}

Then we recompile class A BUT NOT class test, and link them again:

>>g++ -c A.cc
>>g++ A.o test.o –o test

Then we run test program:
>>./test

and get out put:
>>Print from method B

Why not " Print from method A" as I would expect? Let's take a look at the vtable of the revised class A:

vtable of revised class A

A::_ZTV1A: 4u entries
0 0u
8 (int (*)(...))(&_ZTI1A)
16 A::methodB
24 A::methodA

Note the address of methodA is now with offset 24, while the address of 16 is occupied by methodB. If we execute test program without recompilation, the address with offset 16 is still referenced and thus methodB gets called.

This problem shown above is known as "constant recompilation problem" and is usually considered as a side effect of C++: because C++ compiler references methods or variables by numeric offset at compilation time, sometimes we add a new method or a new instance variable to a class, any and all classes that reference that class will require a recompilation, or they break.







3 comments:

Marnix Klooster said...

I might be wrong, but I'm pretty sure the C++ standard does not require the use of vtables.

Anonymous said...

Thanks for the nice short post.

I think C++ needs the vtable, otherwise no virtual functions could be realized.

Anonymous said...

There are other ways to achieve virtual functions... Smalltalk, Ruby, and Python don't use vtables for instance.

That said, while the C++ standard doesn't directly require vtables, it essentially does indirectly. While it's theoretically possible to implement it another way, there's no reason to: the other ways are slower, and they don't buy you anything in a statically-typed language like C++.