Demystifying Virtual Tables in C++ – Part 2 Non-virtual inheritance

Introduction

In this post, we will continue our dive into the C++ dynamic dispatch. So far we have verified that gdb does not create a virtual table for simple classes and default constructors [Part 1 Trivial Constructors]. In part 2 we will look at non-virtual derived classes, their construction, and memory layout.

When creating a class, instead of writing completely new data members and member functions, the programmer can designate that the new class should inherit the members of an existing class. This existing class is called the base class, and the new class is referred to as the derived class.

I assume that readers have a familiarity with C++. I will not be explaining why a class should be derived from another class neither will I explain assembly in detail. We will verify common knowledge about c++ using the gdb debugger on a 64bit Ubuntu Linux machine.

Non-virtual function hiding

Let me introduce two new classes. A base class Base and a derived class that inherits from base called Derived. Note that both use the same signature of their method method(). With dynamic dispatch in place, the vtable would be consulted and the appropriate method would be called. Without dynamic dispatch, the method will be called that matched the type of the pointer to the object. A Derived* ptr will call Derived::method() and a Base* ptr will call Base::method(). Let us verify the assembly.

We see that the method call is baked into the machine code. There is no possibility for runtime decisions. The type of the pointer is known at compile time and the compiler picks the correct method call. It might be what we want or it might not. It serves as a good example. For completeness sake we can always access the base method by defining the namespace of the base class like this:

To understand where the virtual table is hidden, let us first examine the memory layout of a simple inheritance hierarchy. Let us add some integer variables to the Derived and Base class. The sizeof (int) on this particular machine, using this particular compiler is 4 bytes. I always try to keep in mind that this number by no means is guaranteed and can change over time.

Memory locations starting with low numbers (in this case 0x602010) are variables on the heap. Variables located on the stack usually have a very high number (0x7fffffffe3b0). It is common knowledge that the heap grows upwards and the stack grows downwards. How the objects are organised in a stack frame is highly dependant on the compiler used. Some values can be completely optimised out and replaced by registers.

Both the base pointer and the derived pointer point to the first byte of the class. As we will be able to verify in the assembly: the base class is followed by the derived class at a higher address. This simple exercise will help us in the future in finding the virtual pointer.

A quick look at the disassembly and the examine command verifies our findings.

Order of Construction

In this disassembly fragment I intentionally ‘forgot’ to initialize class members. None of the variables Base.a and Derived.b are mentioned in the initialization list nor in the constructor. As we can see, the derived constructor manipulates the stack pointer and calls the base constructor. The base constructor manipulates the stack pointer and does nothing. In the next segment, we will see what is missing.

Initializing Members

Let us remove the error we did in the previous fragment. Both variables are initialized either in the initialization list, the constructor or using new C++11 semantics. Base.a is initialized to 128 which is 0x80 in hex. We will see this value in the assembly. Derived.b is initialized to 256 which is 0x100 in hex.

The base class is fully constructed first without any changes to the members of the derived class. This will be especially important when we introduce virtual tables as this order defines what functions are visible at what stage. Stay tuned for part three.

If you’ve read so far, did you notice that the code is leaking (the main method)? I hope you did!