<< PreviousNext >>

Answer to Wrong function called

The first step to understand this problem is to identify the undefined behaviour. It is actually hidden inside the variable argument list. What happens is more or less this:

Text o("a");
void *t = (void *)o;
Printable *p = (Printable *)t;

It is actually undefined what happens when you cast a void * to something else than what you created the void * from, even though you could argue that the pointer was originally a Printable. This is however not the case, and to understand why this happens we require some insight into how virtual function calls and multiple inheritance is implemented in C++.

To implement virtual function calls, the compiler will insert a pointer to a vtable as the (usually) first member of your class. The vtable is more or less a list of pointers to the virtual functions for this instance. For example, in the case of the Printable object, the compiler will know that entry 1 (for example) is the pointer to the function that overloads the toS method.

This is fairly easy to understand when not having multiple inheritance. If a subclass wants to add more virtual functions, it can just extend the vtable and add new entries there. The base class is not aware of them, so it does not hurt any code expecting a base class. However, how do we do when dealing with multiple inheritance?

This is where things get complicated. We have to somehow create an object that looks like the Printable class sometimes, and the StrType class sometimes. This is because when some code wants to call the toS in a Printable object, the generated code will call the function located at the first position in the vtable. However, generated code for calling type in the StrType class will also look at the first position. How do we solve this?

The solution is to create multiple vtables and sometimes alter the pointer to the object. In this case, the Text object will look something like this:

Offset (32-bit systems) Data
0 vtable compatible with StrType
- all members in StrType (none present here)
4 vtable compatible with Printable
- all members in Printable (none present here)
8 the text member

So what happens when we cast from a Text* to a Printable* via a void* is that we will end up with machine code that does not take the fact that we need to alter the pointer by 4 bytes in this case, so we end up getting an object that looks like a StrType object instead. After that, we're just lucky that the function type has the same signature as toS, otherwise our program would most certainly have crashed somehow.

To make the code work as expected, we have a few options:

  • We could reorder the base classes for our Text object, so that Printable is the first base class. This elliminates the need for adjusting the pointer in this case. It would also work to remove the StrType base class.
  • We could rewrite the myPrint function to use variadic templates present in C++11. This would keep the type information so that the compiler can generate correct code.

Problem

Comments

New comment

You can use GitHub flavored markdown here. Parsed by Parsedown, which does not support all of GitHub's features. For example, specifying the language of code listings is not supported.

Name: