MEMORY LAYOUT OF C++ OBJECT
Reading Time: 6 minutes

In this article, we will see the memory layout of C++ object. And how different storage classes & access specifiers affect it. We are not going to discuss compiler augmented code, name mangling & working of any C++ mechanism. And I have also ignored the stack growth direction. So that we can understand it in a simple & plain manner.

So, It’s just all about how different object will be represented in memory.

Memory layout of simple & non-polymorphic object in C++

class X
{  
    int x;
    float xx;

  public:
    X(){}
    ~X(){}

    void printInt(){}
    void printFloat(){}
};
  • Memory layout:
      |                        |          
      |------------------------| <------ X class object memory layout
      |        int X::x        |
      |------------------------|  stack segment
      |       float X::xx      |       |   
      |------------------------|       |
      |                        |      \|/
      |                        |    
      |                        |
------|------------------------|----------------
      |         X::X()         | 
      |------------------------|       |   
      |        X::~X()         |       |
      |------------------------|      \|/
      |      X::printInt()     |  text segment
      |------------------------|
      |     X::printFloat()    |
      |------------------------|
      |                        |            
  • As you can see, all data members are in the stack as the same order of their declarations because this is guaranteed by most of the compilers, apparently.
  • All other methods, constructor, destructor & compiler augmented code go into the text segment. These methods are then called & passed this pointer implicitly of calling object in its 1st argument which we have discussed in this article.

Layout of an object with virtual function & static data member

class X
{  
    int x;
    float xx;
    static int count;

  public:
    X(){}
    virtual ~X(){}

    virtual void printAll(){}
    void printInt(){}
    void printFloat(){}
    static void printCount(){}
};
  • Memory layout:
      |                        |          
      |------------------------| <------ X class object memory layout
      |        int X::x        |
stack |------------------------|
  |   |       float X::xx      |                      
  |   |------------------------|      |-------|--------------------------|
  |   |         X::_vptr       |------|       |       type_info X        |
 \|/  |------------------------|              |--------------------------|
      |           o            |              |    address of X::~X()    |
      |           o            |              |--------------------------|
      |           o            |              | address of X::printAll() |
      |                        |              |--------------------------|
      |                        |
------|------------------------|------------
      |  static int X::count   |      /|\
      |------------------------|       |
      |           o            |  data segment           
      |           o            |       |
      |                        |      \|/
------|------------------------|------------
      |        X::X()          | 
      |------------------------|       |   
      |        X::~X()         |       |
      |------------------------|       | 
      |      X::printAll()     |      \|/ 
      |------------------------|  text segment
      |      X::printInt()     |
      |------------------------|
      |     X::printFloat()    |
      |------------------------|
      | static X::printCount() |
      |------------------------|
      |                        |
  • All non-static data members are going into the stack with the same order of their declaration as we have already seen in the previous point.
  • Static data member goes in the data segment of memory and accessed with scope resolution operator. But after compilation, there is nothing like scope & namespace, its just name mangling performed by the compiler, everything referred by its address. You can google this to understand clearly.
  • Static methods go in the text segment and called with scope resolution operator except for this pointer which is not passed in its argument.
  • For virtual keyword, the compiler automatically inserts pointer(_vptr) to a virtual table so it is used to transform direct function calling in an indirect call(you can see that in this article). This virtual table created statically in the data segment, but it also depends on compiler implementation.
  • In a virtual table, 1st entry points to a type_info object which contains information related to current class & DAG(Directed Acyclic Graph) of other base classes if it is derived from them.
  • I have not mentioned the data type of _vptr because the standard does not mention(even I don’t know that).

Layout of C++ object with inheritance

class X
{  
    int x;
    string str;

  public:
    X(){}
    virtual ~X(){}

    virtual void printAll(){}
};

class Y : public X
{
    int y;

  public:
    Y(){}
    ~Y(){}

    void printAll(){}
};
  • Memory layout:
      |                              |          
      |------------------------------| <------ Y class object memory layout
      |          int X::x            |
stack |------------------------------|
  |   |              int string::len |
  |   |string X::str ----------------|
  |   |            char* string::str |         
 \|/  |------------------------------|      |-------|--------------------------|
      |           X::_vptr           |------|       |       type_info Y        |
      |------------------------------|              |--------------------------|
      |          int Y::y            |              |    address of Y::~Y()    |
      |------------------------------|              |--------------------------|
      |               o              |              | address of Y::printAll() |
      |               o              |              |--------------------------|
      |               o              |              
------|------------------------------|--------
      |           X::X()             | 
      |------------------------------|       |   
      |           X::~X()            |       |
      |------------------------------|       | 
      |         X::printAll()        |      \|/ 
      |------------------------------|  text segment
      |           Y::Y()             |
      |------------------------------|
      |           Y::~Y()            |
      |------------------------------|
      |         Y::printAll()        |
      |------------------------------|
      |      string::string()        |
      |------------------------------|
      |      string::~string()       |
      |------------------------------|
      |      string::length()        |
      |------------------------------|
      |               o              |
      |               o              |
      |               o              |
      |                              |
  • In the inheritance model, a base class & data member classes are a subobject of a derived class. And a resultant, memory map looks like the above diagram.
  • All virtual function will be overridden in virtual table & then code for this will be generated in the constructor of the class by the compiler. Which we have discussed in our virtual function series.

Memory layout of an object with multiple inheritances & virtual function

class X {
  public:
    int x;
    virtual ~X(){}
    virtual void printX(){}
};

class Y {
  public:
    int y;
    virtual ~Y(){}
    virtual void printY(){}
};

class Z : public X, public Y {
  public:
    int z;
    ~Z(){}
    void printX(){}
    void printY(){}
    void printZ(){}
};
  • Memory layout:
      |                              |          
      |------------------------------| <------ Z class object memory layout
stack |          int X::x            |
  |   |------------------------------|      |-------|--------------------------|
  |   |          X:: _vptr           |------|       |       type_info Z        |
  |   |------------------------------|              |--------------------------|
 \|/  |          int Y::y            |              |    address of Z::~Z()    |
      |------------------------------|              |--------------------------|
      |          Y:: _vptr           |------|       |   address of Z::printX() |
      |------------------------------|      |       |--------------------------|
      |          int Z::z            |      |
      |------------------------------|      |
      |              o               |      |-------|--------------------------|
      |              o               |              |       type_info Z        |
      |              o               |              |--------------------------|
      |                              |              |    address of Z::~Z()    |
------|------------------------------|---------     |--------------------------|
      |           X::~X()            |       |      |   address of Z::printY() |
      |------------------------------|       |      |--------------------------|   
      |          X::printX()         |       |
      |------------------------------|       | 
      |           Y::~Y()            |      \|/ 
      |------------------------------|  text segment
      |          Y::printY()         |
      |------------------------------|
      |           Z::~Z()            |
      |------------------------------|
      |          Z::printX()         |
      |------------------------------|
      |          Z::printY()         |
      |------------------------------|
      |          Z::printZ()         |
      |------------------------------|
      |               o              |
      |               o              |
      |               o              |
      |                              |
  • In multiple inheritance hierarchy, an exact number of the virtual table created will be N-1, where N represents the number of classes.
  • Now, the rest of the things will be easy to understand for you, I guess so.
  • If you try to call a method of class Z using any base class pointer, then it will be called using the respective virtual table. As an example:
Y *y_ptr = new Z;
y_ptr->printY(); // OK
y_ptr->printZ(); // Not OK, as virtual table of class Y doesn't have address of printZ() method
  • In the above code, y_ptr will point to subobject of class Y within the complete Z object.
  • As a result, call to any method for say y_ptr->printY(); using y_ptr will be resolved like:
 ( *y_ptr->_vtbl[ 2 ] )( y_ptr )
  • You must be wondering why I have passed y_ptr as an argument here. It’s because of an implicit this pointer, you can learn about it here.

Layout of object having virtual inheritance

class X {int x;};
class Y : public virtual X {int y;};
class Z : public virtual X {int z;};
class A : public Y, public Z {int a;};
  • Memory layout:
                  |                |          
 Y class  ------> |----------------| <------ A class object memory layout
sub-object        |   Y::y         |          
                  |----------------|             |------------------| 
                  |   Y::_vptr_Y   |------|      |    offset of X   | // offset(20) starts from Y 
 Z class  ------> |----------------|      |----> |------------------|     
sub-object        |   Z::z         |             |       .....      |     
                  |----------------|             |------------------|  
                  |   Z::_vptr_Z   |------|       
                  |----------------|      |        
 A sub-object --> |   A::a         |      |      |------------------| 
                  |----------------|      |      |    offset of X   | // offset(12) starts from Z
 X class -------> |   X::x         |      |----> |------------------|          
 shared           |----------------|             |       .....      |           
 sub-object       |                |             |------------------|           
  • Memory representation of derived class having one or more virtual base class is divided into two regions:
    1. an invariant region
    2. a shared region
  • Data within the invariant region remains at a fixed offset from the start of the object regardless of subsequent derivations.
  • The shared region contains a virtual base class and it fluctuates with subsequent derivation & order of derivation. I have discussed more on this in here.
Awesome
Awesome Interesting Useful Cool Boring Sucks
31