Page #27 (Common Aspects of C Language)


Virtual Methods in C++ and Memory Layout

When we define a C++ class, we are actually informing the compiler to define the layout for a block of memory. The compiler takes into account the features of the host platform while defining the memory layout. The following code defines an interface class IFoo that has two member variables, namely, m_nFirstValue and m_nSecondValue.

 class IFoo  { public:    int _stdcall GetFirstValue();    int _stdcall GetSecondValue();  private:    int m_nFirstValue;    int m_nSecondValue;  }; 

Figure 1.3 shows the memory layout of this class. The host platform is an Intel machine running Windows 2000 OS.

Figure 1.3. Layout of class IFoo.

Each instance of class IFoo should have enough memory allocated to hold this layout. A pointer to an instance of IFoo contains nothing but the starting address of this memory location.


The methods of the class, including the constructor and the destructor, did not contribute towards the memory layout. This is because these methods were not declared as virtual. Introduction of even one virtual method to the class changes the memory layout, as we will see later.

We will now examine various cases dealing with virtual methods and their implication on memory layout.

Let s revise our class definition to use virtual methods as shown below:

 class IFoo  { public:    virtual int _stdcall GetX();    virtual int _stdcall GetY();  private:    int m_nFirstValue;    int m_nSecondValue;  }; 

Almost all production compilers implement the run-time behavior of virtual member functions using virtual function tables or vtbls. A vtbl is an array of pointers that point to the procedure entry address of each virtual function in a specific class. The memory layout of the class defines an additional entry that points to the vtbl for the class. This entry is called vptr. Figure 1.4 shows the memory layout and vtbl for the class after its methods have been changed to virtual.

Figure 1.4. Layout of class IFoo containing virtual methods.

Consider now, for example, the case when a client that holds a pointer to IFoo calls the method GetY as shown here:

 void sub(IFoo* pFoo)  {   pFoo->GetY();  } 

In the case when the method being called is non-virtual, the compiler generates the following machine language instructions. I have added my comments for illustration.

 mov  eax, DWORD PTR _pFoo$[ebp]  push eax;                        push "this"  call ?GetY@IFoo@@QAGHXZ;         IFoo::GetY 

When the method being called is virtual, the machine language instructions are:

 mov  eax, DWORD PTR _pFoo$[ebp]  mov  ecx, DWORD PTR [eax]           ;load vptr in ecx  mov  edx, DWORD PTR _pFoo$[ebp]  push edx                            ;push "this"  call DWORD PTR [ecx+4]              ;call the method whose                                      ; address is stored in                                      ; second entry of vtbl. 

Each entry in vtbl is four bytes long on my host platform. The first entry is at location [ecx+0], the second at [ecx+4], and so on. The last machine language instruction shown above invokes a method that is stored as the second entry in our vtbl.


By calling a virtual method, we get away from the name-mangling issues, at the expense however, of a couple of more machine language instructions.

Also note the use of the _stdcall function directive. In general, each time a class method is called, the compiler has to make this pointer available to the method being called. Two common techniques to do this are (a) push this on stack and (b) make this available in a machine register. Compiler vendors choose their own technique. For example, the Microsoft Visual C++ compiler uses register ecx to accomplish this, that is, if no directives are specified. By using the _stdcall directive, we are directing the compiler to generate code that pushes this on stack.

Consider the case of overloaded virtual functions, as shown here:

 class IFoo  { public:    virtual int _stdcall GetX();    virtual int _stdcall GetY();    virtual int _stdcall GetX(int y);  private:    int m_nFirstValue;    int m_nSecondValue;  }; 

In this case, the entries in vtbl are not guaranteed to be in order of declaration across all compilers. For example, the layout of the class as generated by the Microsoft s compiler is shown in Figure 1.5.

Figure 1.5. Lay out of class IFoo with overloaded virtual functions.

As can be seen, the third virtual function declaration shows up as the first entry in the vtbl. The implication of this will be explained in the next section.

Consider the case where one interface is derived from another, as shown here:

 class IFoo  { public:    virtual int _stdcall GetX();    virtual int _stdcall GetY();  private:    int m_nFirstValue;    int m_nSecondValue;  };  class IBar : public IFoo  { public:    virtual int _stdcall GetZ();  }; 

The memory layout and the vtbl layout for class IBar are depicted in Figure 1.6.

Figure 1.6. Layout of class IBar.

If you compare the vtbl layout of class IBar with that of class IFoo (from Figure 1.4), you can see that the derived class layout is just a binary extension of the base class layout. Also note that the same vptr serves both the base and the derived classes. We can further generalize this as follows:


In a chain of derived classes, as long as each class is derived from exactly one class, the vtbl of the derived class is a superset of the base class. All the classes in the chain share the same vptr.

Finally, consider the case of multiple inheritances, that is, when a class is derived from more than one base class, as shown here:

 class IFoo  { public:    virtual int _stdcall GetX();    virtual int _stdcall GetY();  private:    int m_nFirstValue;    int m_nSecondValue;  };  class IBar  { public:    virtual int _stdcall GetZ();  };  class IFooBar: public IFoo , public IBar  { public:    virtual int _stdcall GetA();  }; 

The memory and the vtbl layout for class IFooBar is shown in Figure 1.7.

Figure 1.7. Layout of class IFooBar.

Figure 1.7 illustrates that in the case of multiple inheritance, several vptr s are created in the memory layout of the derived class.

Armed with the knowledge we have gained on vtbl implementation, let us see how we can come up with a scheme to obtain compiler independence.


COM+ Programming. A Practical Guide Using Visual C++ and ATL
COM+ Programming. A Practical Guide Using Visual C++ and ATL
ISBN: 130886742
Year: 2000
Pages: 129 © 2008-2017.
If you may any questions please contact us: