C++ object memory layout

Factors that may affect the memory layout of C++ objects:

  1. object data member variables
  2. General member functions of objects
  3. virtual member function of object
  4. Object inheritance - parent class does not contain virtual functions
  5. Object inheritance - parent class contains virtual functions
  6. Object multiple inheritance - parent class does not contain virtual functions
  7. Object multiple inheritance - parent class contains virtual functions
  8. Object virtual inheritance
  9. Dynamic type information (not discussed here)
  10. See the virtual function calling process through assembly code

The implementation of these features often varies depending on the operating system platform and compiler/ABI. The analysis here is mainly based on the behavior of GCC 11.2.0/G++ 11.2.0 on the linux x86_64 platform.

Memory layout of general C++ objects

Generally, C++ objects refer to C++ objects with only a few data member variables. They are the simplest class objects in C++. A class defined with classthe keyword is exactly the same as a structure in the C language, except for the default visibility of member variables.

Here define a general C++ class, instantiate an object, and check the address of the object and the addresses of each data member variable:

class Object;
void print_field_address(Object *obj);

class Object {
public:
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  int32_t data_4 = 0;
private:
  int32_t data_5 = 0;
  int32_t data_6 = 0;

  friend void print_field_address(Object *obj);
};

void print_field_address(Object *obj) {
  printf("Address of obj %p, object size %zu bytes\n", obj, sizeof(*obj));
  printf("Address of Field data_1 %p\n", &obj->data_1);
  printf("Address of Field data_2 %p\n", &obj->data_2);
  printf("Address of Field data_3 %p\n", &obj->data_3);
  printf("Address of Field data_4 %p\n", &obj->data_4);
  printf("Address of Field data_5 %p\n", &obj->data_5);
  printf("Address of Field data_6 %p\n", &obj->data_6);
}

int main(int argc, char *argv[]) {
  Object obj;
  print_field_address(&obj);

  return 0;
}

The output of the above code may look like this:

Address of obj 0x7fff3c605fa0, object size 32 bytes
Address of Field data_1 0x7fff3c605fa0
Address of Field data_2 0x7fff3c605fa4
Address of Field data_3 0x7fff3c605fa8
Address of Field data_4 0x7fff3c605fb0
Address of Field data_5 0x7fff3c605fb4
Address of Field data_6 0x7fff3c605fb8

If executed on different machines, or executed multiple times on the same machine, the output of each execution may be different, but the difference in the specific memory addresses of the object and each member variable does not affect the analysis conclusion.

You can see from the address output above:

  • Generally, the address of a C++ object is the same as the address of its first member variable;
  • Generally, each data member of a C++ object is arranged one by one in the memory in the order in which they are declared;
  • There may be padding between individual data members due to the data type of the member variable.
  • The size of the object is larger than the actual sum of the sizes of its members due to padding. In the example object, padding occurs in two places.

Memory layout of general C++ objects with member functions

Here we add several member functions to the general C++ class above, and then look at its class object memory layout. The sample code is as follows:

class Object;
void print_field_address(Object *obj);

class Object {
public:
  Object();
  ~Object();
  void funcA();
  void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int32_t data_6 = 0;

  friend void print_field_address(Object *obj);
};

Object::Object() {}
Object::~Object() {}
void Object::funcA() {}
void Object::funcB() {}
void Object::funcC() {}
void Object::funcD() {}
void Object::funcE() {}

void print_field_address(Object *obj) {
  printf("Address of obj %p, object size %zu bytes\n", obj, sizeof(*obj));
  printf("Address of Field data_1 %p\n", &obj->data_1);
  printf("Address of Field data_2 %p\n", &obj->data_2);
  printf("Address of Field data_3 %p\n", &obj->data_3);
  printf("Address of Field data_4 %p\n", &obj->data_4);
  printf("Address of Field data_5 %p\n", &obj->data_5);
  printf("Address of Field data_6 %p\n", &obj->data_6);
}

int main(int argc, char *argv[]) {
  Object obj;
  print_field_address(&obj);

  return 0;
}

The output of the above code may look like this:

Address of obj 0x7ffe6d94ba00, object size 32 bytes
Address of Field data_1 0x7ffe6d94ba00
Address of Field data_2 0x7ffe6d94ba04
Address of Field data_3 0x7ffe6d94ba08
Address of Field data_4 0x7ffe6d94ba10
Address of Field data_5 0x7ffe6d94ba14
Address of Field data_6 0x7ffe6d94ba18

Since class member functions belong to the class and not to the class object, adding member functions has no impact on the memory layout of the class object. It can be seen from the printed address of the class object and the addresses of each member variable of the class object.

Memory layout of C++ class objects with virtual functions

Here we change some of the member functions of the class with member functions above into virtual functions, and then look at its class object memory layout. A class with virtual functions will implicitly contain a pointer to the virtual function table in its object. In order to determine the location of the virtual function table pointer, a class is added here that contains two data member variables, and the object of the class containing the virtual function is embedded between the two member variables. The sample code is as follows:

class Object;
void print_field_address(Object *obj);

class Object {
public:
  Object();
  virtual ~Object();
  void funcA();
  virtual void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  void funcC();
  int32_t data_4 = 0;
private:
  virtual void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  friend void print_field_address(Object *obj);
};

Object::Object() {}
Object::~Object() {}
void Object::funcA() {}
void Object::funcB() {}
void Object::funcC() {}
void Object::funcD() {}
void Object::funcE() {}

void print_field_address(Object *obj) {
  printf("Address of obj %p, object size %zu bytes\n", obj, sizeof(*obj));
  printf("Address of Field data_1 %p\n", &obj->data_1);
  printf("Address of Field data_2 %p\n", &obj->data_2);
  printf("Address of Field data_3 %p\n", &obj->data_3);
  printf("Address of Field data_4 %p\n", &obj->data_4);
  printf("Address of Field data_5 %p\n", &obj->data_5);
  printf("Address of Field data_6 %p\n", &obj->data_6);
}

class Object1 {
public:
  int64_t data_10 = 0;
  int64_t data_11 = 0;
  Object obj;
  int32_t data_12 = 0;
};

int main(int argc, char *argv[]) {
  Object1 obj;
  printf("Pointer size %zu bytes\n", sizeof(void *));
  printf("Address of Field data_10 %p\n", &obj.data_10);
  printf("Address of Field data_11 %p\n", &obj.data_11);
  print_field_address(&obj.obj);
  printf("Address of Field data_12 %p\n", &obj.data_12);


  return 0;
}

The output of the above code is as follows:

Pointer size 8 bytes
Address of Field data_10 0x7ffca60e3cf0
Address of Field data_11 0x7ffca60e3cf8
Address of obj 0x7ffca60e3d00, object size 40 bytes
Address of Field data_1 0x7ffca60e3d08
Address of Field data_2 0x7ffca60e3d0c
Address of Field data_3 0x7ffca60e3d10
Address of Field data_4 0x7ffca60e3d18
Address of Field data_5 0x7ffca60e3d1c
Address of Field data_6 0x7ffca60e3d20
Address of Field data_12 0x7ffca60e3d28

On my 64-bit machine, the length of the pointer variable is 8 bytes. For an object of a class with virtual functions, the difference between its object address and the address of the first data member variable is 8, which is the length of a pointer variable. There can be no gap between the starting address of the object and the data in front of it. The object The last data member can also have no gaps between it and the data that follows it.

For C++ class objects with virtual functions, the memory layout is:

  • The virtual function table pointer is located at the beginning of the class object, and the address of the virtual function table pointer is the same as the address of the class object;
  • The number of virtual functions, the location of virtual function declaration, etc. do not affect the memory layout of class objects;
  • Following the virtual function table pointer are other data member variables, which are arranged like ordinary C++ class objects.

Memory layout of subclass objects in single inheritance

Here we design a class to inherit a general C++ class with member functions and data member variables, and view the memory layout of its class objects. The sample code is as follows:

class Object;
void print_field_address(Object *obj);

class Object {
public:
  Object();
  ~Object();
  void funcA();
  void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  friend void print_field_address(Object *obj);
};

Object::Object() {}
Object::~Object() {}
void Object::funcA() {}
void Object::funcB() {}
void Object::funcC() {}
void Object::funcD() {}
void Object::funcE() {}

void print_field_address(Object *obj) {
  printf("Address of obj %p, object size %zu bytes\n", obj, sizeof(*obj));
  printf("Address of Field data_1 %p\n", &obj->data_1);
  printf("Address of Field data_2 %p\n", &obj->data_2);
  printf("Address of Field data_3 %p\n", &obj->data_3);
  printf("Address of Field data_4 %p\n", &obj->data_4);
  printf("Address of Field data_5 %p\n", &obj->data_5);
  printf("Address of Field data_6 %p\n", &obj->data_6);
}

class Object1 : public Object {
public:
  int64_t data_10 = 0;
  int64_t data_11 = 0;
  int32_t data_12 = 0;
};

int main(int argc, char *argv[]) {
  Object1 obj;
  printf("Pointer size %zu bytes, obj1 address %p, obj1 size %zu bytes\n",
      sizeof(void *), &obj, sizeof(obj));
  print_field_address(&obj);
  printf("Address of Field data_10 %p\n", &obj.data_10);
  printf("Address of Field data_11 %p\n", &obj.data_11);
  printf("Address of Field data_12 %p\n", &obj.data_12);

  return 0;
}

The output of the above code is as follows:

Pointer size 8 bytes, obj1 address 0x7ffff0033c90, obj1 size 56 bytes
Address of obj 0x7ffff0033c90, object size 32 bytes
Address of Field data_1 0x7ffff0033c90
Address of Field data_2 0x7ffff0033c94
Address of Field data_3 0x7ffff0033c98
Address of Field data_4 0x7ffff0033ca0
Address of Field data_5 0x7ffff0033ca4
Address of Field data_6 0x7ffff0033ca8
Address of Field data_10 0x7ffff0033cb0
Address of Field data_11 0x7ffff0033cb8
Address of Field data_12 0x7ffff0033cc0

In single inheritance, the memory layout of subclass objects is as follows:

  • The starting position of the subclass object saves the content of the parent class object;
  • The subclass's own data members come after all the contents of the parent class, and there may be padding in between;
  • The data members of a subclass are arranged in memory in the order they are declared.

When the parent class has virtual functions, the basic logic of single inheritance is the same, that is, the content of the parent class object is saved at the beginning of the child class object, but at this time the content of the parent class object will contain the parent class's virtual function table pointer. The sample code is as follows:

class Object;
void print_field_address(Object *obj);

class Object {
public:
  Object();
  virtual ~Object();
  virtual void funcA();
  void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  virtual void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  friend void print_field_address(Object *obj);
};

Object::Object() {}
Object::~Object() {}
void Object::funcA() {}
void Object::funcB() {}
void Object::funcC() {}
void Object::funcD() {}
void Object::funcE() {}

void print_field_address(Object *obj) {
  printf("Address of obj %p, object size %zu bytes\n", obj, sizeof(*obj));
  printf("Address of Field data_1 %p\n", &obj->data_1);
  printf("Address of Field data_2 %p\n", &obj->data_2);
  printf("Address of Field data_3 %p\n", &obj->data_3);
  printf("Address of Field data_4 %p\n", &obj->data_4);
  printf("Address of Field data_5 %p\n", &obj->data_5);
  printf("Address of Field data_6 %p\n", &obj->data_6);
}

class Object1 : public Object {
public:
  void funcA() override;
  void funcC() override;
  int64_t data_10 = 0;
  int64_t data_11 = 0;
  int32_t data_12 = 0;
};

void Object1::funcA() {}
void Object1::funcC() {}

int main(int argc, char *argv[]) {
  Object1 obj;
  printf("Pointer size %zu bytes, obj1 address %p, obj1 size %zu bytes\n",
      sizeof(void *), &obj, sizeof(obj));
  print_field_address(&obj);
  printf("Address of Field data_10 %p\n", &obj.data_10);
  printf("Address of Field data_11 %p\n", &obj.data_11);
  printf("Address of Field data_12 %p\n", &obj.data_12);

  return 0;
}

The output of the above code is as follows:

Pointer size 8 bytes, obj1 address 0x7ffd42bad690, obj1 size 64 bytes
Address of obj 0x7ffd42bad690, object size 40 bytes
Address of Field data_1 0x7ffd42bad698
Address of Field data_2 0x7ffd42bad69c
Address of Field data_3 0x7ffd42bad6a0
Address of Field data_4 0x7ffd42bad6a8
Address of Field data_5 0x7ffd42bad6ac
Address of Field data_6 0x7ffd42bad6b0
Address of Field data_10 0x7ffd42bad6b8
Address of Field data_11 0x7ffd42bad6c0
Address of Field data_12 0x7ffd42bad6c8

Whether the subclass overrides the virtual function of the parent class and the number of overridden virtual functions of the parent class do not affect the memory layout of the subclass object.

Memory layout of subclass objects in multiple inheritance

Here we design a class that inherits two parent classes. Be lazy and make the definitions of the two parent classes basically the same. The sample code is as follows:

template<typename Object>
void print_field_address(Object *obj);

class Object {
public:
  Object();
  ~Object();
  void funcA();
  void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  friend void print_field_address<Object>(Object *obj);
};

Object::Object() {}
Object::~Object() {}
void Object::funcA() {}
void Object::funcB() {}
void Object::funcC() {}
void Object::funcD() {}
void Object::funcE() {}

class Object1 {
public:
  Object1();
  ~Object1();
  void funcA();
  void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  friend void print_field_address<Object1>(Object1 *obj);
};

Object1::Object1() {}
Object1::~Object1() {}
void Object1::funcA() {}
void Object1::funcB() {}
void Object1::funcC() {}
void Object1::funcD() {}
void Object1::funcE() {}

template<typename Object>
void print_field_address(Object *obj) {
  printf("Address of obj %p, object size %zu bytes\n", obj, sizeof(*obj));
  printf("Address of Field data_1 %p\n", &obj->data_1);
  printf("Address of Field data_2 %p\n", &obj->data_2);
  printf("Address of Field data_3 %p\n", &obj->data_3);
  printf("Address of Field data_4 %p\n", &obj->data_4);
  printf("Address of Field data_5 %p\n", &obj->data_5);
  printf("Address of Field data_6 %p\n", &obj->data_6);
}

class Object2: public Object, public Object1 {
public:
  int64_t data_10 = 0;
  int64_t data_11 = 0;
  int32_t data_12 = 0;
};

int main(int argc, char *argv[]) {
  Object2 obj2;
  printf("obj2 address %p, obj2 size %zu bytes\n", &obj2, sizeof(obj2));
  print_field_address<Object>(&obj2);
  print_field_address<Object1>(&obj2);
  printf("Address of Field data_10 %p\n", &obj2.data_10);
  printf("Address of Field data_11 %p\n", &obj2.data_11);
  printf("Address of Field data_12 %p\n", &obj2.data_12);

  return 0;
}

The output of the above code is as follows:

obj2 address 0x7fff4a92dea0, obj2 size 88 bytes
Address of obj 0x7fff4a92dea0, object size 32 bytes
Address of Field data_1 0x7fff4a92dea0
Address of Field data_2 0x7fff4a92dea4
Address of Field data_3 0x7fff4a92dea8
Address of Field data_4 0x7fff4a92deb0
Address of Field data_5 0x7fff4a92deb4
Address of Field data_6 0x7fff4a92deb8
Address of obj 0x7fff4a92dec0, object size 32 bytes
Address of Field data_1 0x7fff4a92dec0
Address of Field data_2 0x7fff4a92dec4
Address of Field data_3 0x7fff4a92dec8
Address of Field data_4 0x7fff4a92ded0
Address of Field data_5 0x7fff4a92ded4
Address of Field data_6 0x7fff4a92ded8
Address of Field data_10 0x7fff4a92dee0
Address of Field data_11 0x7fff4a92dee8
Address of Field data_12 0x7fff4a92def0

In multiple inheritance, the memory layout of subclass objects is as follows:

  • The contents of each parent class object are arranged in sequence at the beginning of the subclass object. The contents of each parent class object are arranged in the order of declaration. There may be padding between the contents of each parent class object;
  • The subclass's own data members come after all the contents of all parent classes, and there may be padding in between;
  • The data members of a subclass are arranged in memory in the order they are declared.

static_cast reinterpret_castHere you can see the different behaviors when the parent class object pointer is converted into a child class object pointer in multiple inheritance . The sample code is as follows:

int main(int argc, char *argv[]) {
  Object2 obj2;
  Object1 *obj1p = &obj2;
  Object2 *obj2p = static_cast<Object2 *>(obj1p);
  Object2 *obj2p2 = reinterpret_cast<Object2 *>(obj1p);
  printf("obj1p %p, obj2p %p, obj2p2 %p\n", obj1p, obj2p, obj2p2);

  return 0;
}

The output of the above code is as follows:

obj1p 0x7ffc763ca260, obj2p 0x7ffc763ca240, obj2p2 0x7ffc763ca260

It can be seen that when static_castconverting the pointer type, the value of the pointer may be adjusted according to the type information. However, when reinterpret_castconverting the pointer type, the type of one pointer is mindlessly forced to another type, and the pointer value will not be adjusted according to the type information. Type adjustment.

In this example code of multiple inheritance, neither parent class has virtual functions. When two parent classes have virtual functions, as long as the function signatures of the virtual functions of the two parent classes are different, there will be no impact on the memory layout of the child class object, except that the content of the parent class object will include the virtual function table of the parent class. pointer. The sample code is as follows:

template<typename Object>
void print_field_address(Object *obj);

class Object {
public:
  Object();
  virtual ~Object();
  void funcA();
  virtual void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  friend void print_field_address<Object>(Object *obj);
};

Object::Object() {}
Object::~Object() {}
void Object::funcA() {}
void Object::funcB() {}
void Object::funcC() {}
void Object::funcD() {}
void Object::funcE() {}

class Object1 {
public:
  Object1();
  virtual ~Object1();
  void funcA();
  void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  virtual void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  friend void print_field_address<Object1>(Object1 *obj);
};

Object1::Object1() {}
Object1::~Object1() {}
void Object1::funcA() {}
void Object1::funcB() {}
void Object1::funcC() {}
void Object1::funcD() {}
void Object1::funcE() {}

template<typename Object>
void print_field_address(Object *obj) {
  printf("Address of obj %p, object size %zu bytes\n", obj, sizeof(*obj));
  printf("Address of Field data_1 %p\n", &obj->data_1);
  printf("Address of Field data_2 %p\n", &obj->data_2);
  printf("Address of Field data_3 %p\n", &obj->data_3);
  printf("Address of Field data_4 %p\n", &obj->data_4);
  printf("Address of Field data_5 %p\n", &obj->data_5);
  printf("Address of Field data_6 %p\n", &obj->data_6);
}

class Object2: public Object, public Object1 {
public:
  virtual ~Object2();
  virtual void funcF();
  int64_t data_10 = 0;
  int64_t data_11 = 0;
  int32_t data_12 = 0;
};

Object2::~Object2() {}

void Object2::funcF(){}

int main(int argc, char *argv[]) {
  Object2 obj2;
  printf("obj2 address %p, obj2 size %zu bytes\n", &obj2, sizeof(obj2));
  print_field_address<Object>(&obj2);
  print_field_address<Object1>(&obj2);
  printf("Address of Field data_10 %p\n", &obj2.data_10);
  printf("Address of Field data_11 %p\n", &obj2.data_11);
  printf("Address of Field data_12 %p\n", &obj2.data_12);

  return 0;
}

In order to further understand the impact of declaring virtual functions on the memory layout of class objects, in addition to declaring several virtual functions for the two parent classes, additional virtual functions are also specifically declared for the subclasses. The output of the above code is as follows:

obj2 address 0x7ffe732ac990, obj2 size 104 bytes
Address of obj 0x7ffe732ac990, object size 40 bytes
Address of Field data_1 0x7ffe732ac998
Address of Field data_2 0x7ffe732ac99c
Address of Field data_3 0x7ffe732ac9a0
Address of Field data_4 0x7ffe732ac9a8
Address of Field data_5 0x7ffe732ac9ac
Address of Field data_6 0x7ffe732ac9b0
Address of obj 0x7ffe732ac9b8, object size 40 bytes
Address of Field data_1 0x7ffe732ac9c0
Address of Field data_2 0x7ffe732ac9c4
Address of Field data_3 0x7ffe732ac9c8
Address of Field data_4 0x7ffe732ac9d0
Address of Field data_5 0x7ffe732ac9d4
Address of Field data_6 0x7ffe732ac9d8
Address of Field data_10 0x7ffe732ac9e0
Address of Field data_11 0x7ffe732ac9e8
Address of Field data_12 0x7ffe732ac9f0

The size of this subclass object is only 16 bytes more than the previous version that does not contain any virtual functions in the entire inheritance system, which is the size of two pointers. This also means that subclasses do not have dedicated vtable pointers.

The memory layout of the Object2 class object here is as shown in the figure below.

 Low   |                                  |          
   |   |----------------------------------| <------ Object2 class object memory layout
   |   |           Object::vptr.          |---------|
   |   |----------------------------------|         |---------> |----------------------------|
   |   |     int32_t Object:: data_1      |                     |           . . .            |
  \|/  |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_2      |                     |           . . .            |
       |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_3      |
       |----------------------------------|
       |     int32_t Object:: data_4      | 
       |----------------------------------|
       |     int32_t Object:: data_5      |
       |----------------------------------|
       |     int32_t Object:: data_6      | 
       |----------------------------------|
       |          Object1::vptr           |---------|
       |----------------------------------|         |
       |     int32_t Object1:: data_1     |         |---------> |----------------------------|
       |----------------------------------|                     |           . . .            |
       |     int32_t Object1:: data_2     |                     |----------------------------|
       |----------------------------------|                     |           . . .            |
       |     int32_t Object1:: data_3     |                     |----------------------------|
       |----------------------------------|
       |     int32_t Object1:: data_4     |
       |----------------------------------|
       |     int32_t Object1:: data_5     |
       |----------------------------------|
       |     int32_t Object1:: data_6     |
       |----------------------------------|
       |    int64_t Object2:: data_10     |
       |----------------------------------|
       |    int64_t Object2:: data_11     |
       |----------------------------------|
       |    int64_t Object2:: data_12     |
-------|----------------------------------|------------
       |                o                 |
       |                o                 |
       |                o                 |
       |                                  |
       |                                  |

C++ virtual function calls and the structure of the virtual function table

After understanding the location of the virtual function table pointer in the C++ class object, it is not difficult for us to use the virtual function table pointer to simulate the call of the virtual function. The sample code is as follows:

template<typename Object>
void print_field_address(Object *obj);

class Object {
public:
  Object();
  virtual ~Object();
  void funcA();
  virtual void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  friend void print_field_address<Object>(Object *obj);
};

Object::Object() { printf("Object::ctor()\n"); }
Object::~Object() { printf("Object::~dtor()\n"); }
void Object::funcA() { printf("Object::funcA()\n"); }
void Object::funcB() { printf("Object::funcB()\n"); }
void Object::funcC() { printf("Object::funcC()\n"); }
void Object::funcD() { printf("Object::funcD()\n"); }
void Object::funcE() { printf("Object::funcE()\n"); }

class Object1 {
public:
  Object1();
  virtual ~Object1();
  void funcA();
  void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  virtual void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  friend void print_field_address<Object1>(Object1 *obj);
};

Object1::Object1() { printf("Object1::ctor()\n"); }
Object1::~Object1() { printf("Object1::~dtor()\n"); }
void Object1::funcA() { printf("Object1::funcA()\n"); }
void Object1::funcB() { printf("Object1::funcB()\n"); }
void Object1::funcC() { printf("Object1::funcC()\n"); }
void Object1::funcD() { printf("Object1::funcD()\n"); }
void Object1::funcE() { printf("Object1::funcE()\n"); }

class Object2: public Object, public Object1 {
public:
  Object2();
  virtual ~Object2();
  virtual void funcF();
  int64_t data_10 = 0;
  int64_t data_11 = 0;
  int32_t data_12 = 0;
};

Object2::Object2() { printf("Object2::ctor()\n"); }
Object2::~Object2() { printf("Object2::~dtor()\n"); }

void Object2::funcF(){ printf("Object2::funcF()\n"); }

int main(int argc, char *argv[]) {
  Object2 obj2;
  Object2 obj2_01;

  printf("obj2 address %p, obj2 size %zu bytes\n", &obj2, sizeof(obj2));

  printf("Object vtable address %p, Object1 vtable address %p\n",
      *reinterpret_cast<void **>(&obj2),
      *reinterpret_cast<void **>(static_cast<Object1 *>(&obj2)));

  printf("Object vtable address %p, Object1 vtable address %p\n",
        *reinterpret_cast<void **>(&obj2_01),
        *reinterpret_cast<void **>(static_cast<Object1 *>(&obj2_01)));

  void ** vtable = (void**)(*reinterpret_cast<void **>(&obj2));
  void ** child_vtable = (void**)(*reinterpret_cast<void **>(static_cast<Object1 *>(&obj2)));

  printf("vtable[0] %p, vtable[1] %p, child_vtable[0] %p, child_vtable[1] %p\n",
      vtable[0], vtable[1], child_vtable[0], child_vtable[1]);

  for (int i = 2; i < 10; ++i) {
    void (*vmem_func)(Object *) = reinterpret_cast<void (*)(Object *)>(vtable[i]);
    printf("vmem_func %p start %d\n", vmem_func, i);
    vmem_func(&obj2);
    printf("vmem_func end\n");
  }

  return 0;
}

In the above code, we first obtain the address of the virtual function table pointer, which is the address of the class object. We cast the address of the class object into a pointer to the pointer, and then dereference it, thus obtaining the virtual function The address of the table; the virtual function table is an array of function pointers, that is, an array of pointers, so the pointer pointing to the virtual function table is cast to an array of pointers; the prototype of the function pointed to by each function pointer in the virtual function table, except for the first Except for the first parameter which is a class object pointer, the other parameters are the parameters in the class virtual function declaration. Here, since the function prototypes of each virtual function are the same, some trouble is saved.

The output of the above code is as follows:

Object::ctor()
Object1::ctor()
Object2::ctor()
Object::ctor()
Object1::ctor()
Object2::ctor()
obj2 address 0x7ffd921d19e0, obj2 size 104 bytes
Object vtable address 0x558b593eec80, Object1 vtable address 0x558b593eecb0
Object vtable address 0x558b593eec80, Object1 vtable address 0x558b593eecb0
vtable[0] 0x558b593ec5c2, vtable[1] 0x558b593ec628, child_vtable[0] 0x558b593ec61d, child_vtable[1] 0x558b593ec657
vmem_func 0x558b593ec300 start 2
Object::funcB()
vmem_func end
vmem_func 0x558b593ec662 start 3
Object2::funcF()
vmem_func end
vmem_func 0xffffffffffffffd8 start 4

By calling each function pointed to in the virtual function table of each C++ class object through the above method, you can find the structure of the virtual function table:

  • The virtual function table belongs to the class, not the class object. Although each class object may have one or more virtual function table pointers, the corresponding virtual function table pointers point to the same pointer between different instances of the same class. virtual function table;
  • The first two elements of the virtual function table point to the destructor of the class object, of which the second destructor is the version that releases memory;
  • The destructors pointed to in the virtual function tables of the two parent classes are not exactly the same, but the codes executed by these destructors are almost identical;
  • The subclass does not have a separate virtual function table, but shares a virtual function table with its first parent class;
  • The structure of the virtual function table of the first parent class of a subclass is: the virtual function of the first parent class, followed by the virtual function unique to the subclass;
  • The address of the vtable of the first parent class is very close to the address of the vtable of the second parent class, as if they are one and the same.

Supplementing the internal structure of the virtual function table, the memory layout of the Object2 class object is as shown in the figure below.

 Low   |                                  |          
   |   |----------------------------------| <------ Object2 class object memory layout
   |   |           Object::vptr.          |---------|
   |   |----------------------------------|         |---------> |----------------------------|
   |   |     int32_t Object:: data_1      |                     |    Object2::~Object2()     |
  \|/  |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_2      |                     |    Object2::~Object2()     |
       |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_3      |                     |      Object::funcB()       |
       |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_4      |                     |      Object2::funcF()      |
       |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_5      |
       |----------------------------------|
       |     int32_t Object:: data_6      |
       |----------------------------------|
       |          Object1::vptr           |---------|
       |----------------------------------|         |
       |     int32_t Object1:: data_1     |         |---------> |----------------------------|
       |----------------------------------|                     |    Object2::~Object2()     |
       |     int32_t Object1:: data_2     |                     |----------------------------|
       |----------------------------------|                     |    Object2::~Object2()     |
       |     int32_t Object1:: data_3     |                     |----------------------------|
       |----------------------------------|                     |      Object1::funcC()      |
       |     int32_t Object1:: data_4     |                     |----------------------------|
       |----------------------------------|
       |     int32_t Object1:: data_5     |
       |----------------------------------|
       |     int32_t Object1:: data_6     |
       |----------------------------------|
       |    int64_t Object2:: data_10     |
       |----------------------------------|
       |    int64_t Object2:: data_11     |
       |----------------------------------|         |
       |    int64_t Object2:: data_12     |
-------|----------------------------------|------------
       |                o                 |
       |                o                 |
       |                o                 |
       |                                  |
       |                                  |

C++ Prismatic inheritance and virtual inheritance

In a complex multi-inheritance inheritance hierarchy, prismatic inheritance may occur accidentally, that is, a certain subclass inherits multiple parent classes, and these multiple parent classes inherit the same parent class. Such as the following example:

template<typename Object>
void print_field_address(Object *obj);

class ObjectBase {
public:
  ObjectBase();
  virtual ~ObjectBase();

  void funcA();
  void funcH();

  virtual void funcG();

  int64_t data_31 = 0;
};

ObjectBase::ObjectBase() {}
ObjectBase::~ObjectBase() {}
void ObjectBase::funcA() {}
void ObjectBase::funcG() {}
void ObjectBase::funcH() {}

class Object : public ObjectBase{
public:
  Object();
  virtual ~Object();
  void funcA();
  virtual void funcB(int a);
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  virtual void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  void funcG() override;

  friend void print_field_address<Object>(Object *obj);
};

Object::Object() { printf("Object::ctor()\n"); }
Object::~Object() { printf("Object::~dtor()\n"); }
void Object::funcA() { printf("Object::funcA()\n"); }
void Object::funcB(int a) { printf("Object::funcB()\n"); }
void Object::funcC() { printf("Object::funcC()\n"); }
void Object::funcD() { printf("Object::funcD()\n"); }
void Object::funcE() { printf("Object::funcE()\n"); }
void Object::funcG() { printf("Object::funcG()\n"); }

class Object1 : public ObjectBase{
public:
  Object1();
  virtual ~Object1();
  void funcA();
  virtual void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  virtual void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;
  void funcG() override;

  friend void print_field_address<Object1>(Object1 *obj);
};

Object1::Object1() { printf("Object1::ctor()\n"); }
Object1::~Object1() { printf("Object1::~dtor()\n"); }
void Object1::funcA() { printf("Object1::funcA()\n"); }
void Object1::funcB() { printf("Object1::funcB()\n"); }
void Object1::funcC() { printf("Object1::funcC()\n"); }
void Object1::funcD() { printf("Object1::funcD()\n"); }
void Object1::funcE() { printf("Object1::funcE()\n"); }
void Object1::funcG() { printf("Object1::funcG()\n"); }

template<typename Object>
void print_field_address(Object *obj) {
  printf("Address of obj %p, object size %zu bytes\n", obj, sizeof(*obj));
  printf("Address of Field data_31 %p\n", &obj->data_31);
  printf("Address of Field data_1 %p\n", &obj->data_1);
  printf("Address of Field data_2 %p\n", &obj->data_2);
  printf("Address of Field data_3 %p\n", &obj->data_3);
  printf("Address of Field data_4 %p\n", &obj->data_4);
  printf("Address of Field data_5 %p\n", &obj->data_5);
  printf("Address of Field data_6 %p\n", &obj->data_6);
}

class Object2: public Object, public Object1 {
public:
  Object2();
  virtual ~Object2();
  virtual void funcF();
  void funcG() override;
  int64_t data_10 = 0;
  int64_t data_11 = 0;
  int32_t data_12 = 0;
};

Object2::Object2() { printf("Object2::ctor()\n"); }
Object2::~Object2() { printf("Object2::~dtor()\n"); }

void Object2::funcF(){ printf("Object2::funcF()\n"); }
void Object2::funcG(){ printf("Object2::funcG()\n"); }

int case1(int argc, char *argv[]) {
  Object2 obj2;
  printf("obj2 address %p, obj2 size %zu bytes\n", &obj2, sizeof(obj2));
  print_field_address<Object>(&obj2);
  print_field_address<Object1>(&obj2);
  obj2.funcG();
//  obj2.funcH();
//  obj2.data_31 = 1;

  return 0;
}

In this example, there are two inheritance chains, Object2-> Object-> ObjectBase, Object2-> Object1-> ObjectBase, and Object2the two parent classes of Objectand Object1both inherit the same ObjectBase, thus forming a prismatic inheritance.

In this inheritance hierarchy, ObjectBasethere are two virtual functions (destructor and destructor funcG()) at the top level, a data member and a general member function. Object, Object1and Object2all override ObjectBasethe virtual function of funcG().

The above code compiles and executes without any problem, and the execution results are as follows:

Object::ctor()
Object1::ctor()
Object2::ctor()
obj2 address 0x7fff11beed50, obj2 size 120 bytes
Address of obj 0x7fff11beed50, object size 48 bytes
Address of Field data_31 0x7fff11beed58
Address of Field data_1 0x7fff11beed60
Address of Field data_2 0x7fff11beed64
Address of Field data_3 0x7fff11beed68
Address of Field data_4 0x7fff11beed70
Address of Field data_5 0x7fff11beed74
Address of Field data_6 0x7fff11beed78
Address of obj 0x7fff11beed80, object size 48 bytes
Address of Field data_31 0x7fff11beed88
Address of Field data_1 0x7fff11beed90
Address of Field data_2 0x7fff11beed94
Address of Field data_3 0x7fff11beed98
Address of Field data_4 0x7fff11beeda0
Address of Field data_5 0x7fff11beeda4
Address of Field data_6 0x7fff11beeda8
Object2::funcG()
Object2::~dtor()
Object1::~dtor()
Object::~dtor()

With the help of the above sample code, you can find:

  • Compared with Objectand Object1without inheritance ObjectBase, Object2the size of the object only increases by ObjectBasethe size of two data members. ObjectBaseThe virtual function table Object2does not occupy additional space in the object. In fact, it does not have a special virtual function table itself. The virtual function table Object2associated with the object There are two virtual function tables, one is used to access the virtual function of ( ObjectBase+ Object+ ), and the other is used to access the virtual function of ( + );Object2ObjectBaseObject1
  • If you do not access the members of the top-level base class (member functions, member variables, and virtual member functions), there will be no problem in successfully compiling the code and no problem in executing the code;
  • When only Objectand Object1override ObjectBasethe virtual function of funcG()but Object2not, when the function Object2is called through the object funcG(), the compiler will report an error, similar to the following:
./src/linux_audio.cpp: In function ‘int case1(int, char**)’:
../src/linux_audio.cpp:588:8: error: request for member ‘funcG’ is ambiguous
  588 |   obj2.funcG();
      |        ^~~~~
../src/linux_audio.cpp:490:6: note: candidates are: ‘virtual void ObjectBase::funcG()’
  490 | void ObjectBase::funcG() {}
      |      ^~~~~~~~~~
../src/linux_audio.cpp:552:6: note:                 ‘virtual void Object1::funcG()’
  552 | void Object1::funcG() { printf("Object1::funcG()\n"); }
      |      ^~~~~~~
../src/linux_audio.cpp:522:6: note:                 ‘virtual void Object::funcG()’
  522 | void Object::funcG() { printf("Object::funcG()\n"); }
      |      ^~~~~~
make: *** [src/subdir.mk:26:src/linux_audio.o] 错误 1
"make all" terminated with exit code 2. Build might be incomplete.
  • Object, Object1and Object2all override ObjectBasethe virtual function of funcG(), when the function Object2is called through the object funcG(), there will be no problem with the code whether it is compiled or executed.
  • The data members in the parent class at the top of the inheritance hierarchy have one copy in each of its subclass objects, and there are actually multiple copies in the class objects at the bottom of the inheritance hierarchy.
  • Through Object2the object of the class, ObjectBasethe member function of is accessed. Since the compiler cannot determine whether part Object2of the class object Objector Object1part should be passed to ObjectBasethe member function of, an error occurs during compilation, similar to the following:
../src/linux_audio.cpp:589:8: error: request for member ‘funcH’ is ambiguous
  589 |   obj2.funcH();
      |        ^~~~~
../src/linux_audio.cpp:491:6: note: candidates are: ‘void ObjectBase::funcH()’
  491 | void ObjectBase::funcH() {}
      |      ^~~~~~~~~~
../src/linux_audio.cpp:491:6: note:                 ‘void ObjectBase::funcH()’
make: *** [src/subdir.mk:26:src/linux_audio.o] 错误 1
"make all" terminated with exit code 2. Build might be incomplete.

The virtual inheritance mechanism in C++ is used to solve the problem of ambiguity in symbol parsing and compilation errors when accessing data members of the top-level class in the inheritance hierarchy through the objects of the lowest-level class in the inheritance hierarchy. . We let Object1and Objectvirtual inherit ObjectBase:

template<typename Object>
void print_field_address(Object *obj);

class ObjectBase {
public:
  ObjectBase();
  virtual ~ObjectBase();

  void funcA();
  void funcH();

  virtual void funcG();

  int64_t data_31 = 0;
  int64_t data_32 = 0;
};

ObjectBase::ObjectBase() {}
ObjectBase::~ObjectBase() {}
void ObjectBase::funcA() {}
void ObjectBase::funcG() {}
void ObjectBase::funcH() {}

class Object : virtual public ObjectBase{
public:
  Object();
  virtual ~Object();
  void funcA();
  virtual void funcB(int a);
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  virtual void funcC();
  int32_t data_4 = 0;
private:
  void funcD();
  int32_t data_5 = 0;
  void funcE();
  int64_t data_6 = 0;

  void funcG() override;

  friend void print_field_address<Object>(Object *obj);
};

Object::Object() { printf("Object::ctor()\n"); }
Object::~Object() { printf("Object::~dtor()\n"); }
void Object::funcA() { printf("Object::funcA()\n"); }
void Object::funcB(int a) { printf("Object::funcB()\n"); }
void Object::funcC() { printf("Object::funcC()\n"); }
void Object::funcD() { printf("Object::funcD()\n"); }
void Object::funcE() { printf("Object::funcE()\n"); }
void Object::funcG() { printf("Object::funcG()\n"); }

class Object1 : virtual public ObjectBase{
public:
  Object1();
  virtual ~Object1();
  void funcA();
  void funcB();
  int32_t data_1 = 0;
  int16_t data_2 = 0;
  int64_t data_3 = 0;
  void funcC();
  int32_t data_4 = 0;
private:
  virtual void funcD();
  int32_t data_5 = 0;
  virtual void funcE();
  int64_t data_6 = 0;
  void funcG() override;

  friend void print_field_address<Object1>(Object1 *obj);
};

Object1::Object1() { printf("Object1::ctor()\n"); }
Object1::~Object1() { printf("Object1::~dtor()\n"); }
void Object1::funcA() { printf("Object1::funcA()\n"); }
void Object1::funcB() { printf("Object1::funcB()\n"); }
void Object1::funcC() { printf("Object1::funcC()\n"); }
void Object1::funcD() { printf("Object1::funcD()\n"); }
void Object1::funcE() { printf("Object1::funcE()\n"); }
void Object1::funcG() { printf("Object1::funcG()\n"); }

template<typename Object>
void print_field_address(Object *obj) {
  printf("Address of obj %p, object size %zu bytes\n", obj, sizeof(*obj));

  printf("Address of Field data_1 %p\n", &obj->data_1);
  printf("Address of Field data_2 %p\n", &obj->data_2);
  printf("Address of Field data_3 %p\n", &obj->data_3);
  printf("Address of Field data_4 %p\n", &obj->data_4);
  printf("Address of Field data_5 %p\n", &obj->data_5);
  printf("Address of Field data_6 %p\n", &obj->data_6);

  ObjectBase *base_obj = obj;
  printf("Address of base obj %p, object size %zu bytes\n", base_obj, sizeof(*base_obj));
  printf("Address of Field data_31 %p\n", &obj->data_31);
  printf("Address of Field data_32 %p\n", &obj->data_32);
}

class Object2: public Object, public Object1 {
public:
  Object2();
  virtual ~Object2();
  virtual void funcF();
  void funcG() override;
  int64_t data_10 = 0;
  int64_t data_11 = 0;
  int32_t data_12 = 0;
};

Object2::Object2() { printf("Object2::ctor()\n"); }
Object2::~Object2() { printf("Object2::~dtor()\n"); }

void Object2::funcF(){ printf("Object2::funcF()\n"); }
void Object2::funcG(){ printf("Object2::funcG()\n"); }

int case1(int argc, char *argv[]) {
  Object2 obj2;
  printf("obj2 address %p, obj2 size %zu bytes\n", &obj2, sizeof(obj2));
  print_field_address<Object>(&obj2);
  print_field_address<Object1>(&obj2);
  printf("Address of Field data_10 %p\n", &obj2.data_10);
  printf("Address of Field data_11 %p\n", &obj2.data_11);
  printf("Address of Field data_12 %p\n", &obj2.data_12);
  obj2.funcG();
//  obj2.funcH();
  obj2.data_31 = 1;

  void ** vtable = (void**)(*reinterpret_cast<void **>(static_cast<ObjectBase *>(&obj2)));
  for (int i = 2; i < 10; ++i) {
    void (*vmem_func)(ObjectBase *) = reinterpret_cast<void (*)(ObjectBase *)>(vtable[i]);
    printf("vmem_func %p start %d\n", vmem_func, i);
    vmem_func(&obj2);
    printf("vmem_func end\n");
  }
  return 0;
}

The output of the above code is as follows:

Object::ctor()
Object1::ctor()
Object2::ctor()
obj2 address 0x7ffe8b59eca0, obj2 size 128 bytes
Address of obj 0x7ffe8b59eca0, object size 64 bytes
Address of Field data_1 0x7ffe8b59eca8
Address of Field data_2 0x7ffe8b59ecac
Address of Field data_3 0x7ffe8b59ecb0
Address of Field data_4 0x7ffe8b59ecb8
Address of Field data_5 0x7ffe8b59ecbc
Address of Field data_6 0x7ffe8b59ecc0
Address of base obj 0x7ffe8b59ed08, object size 24 bytes
Address of Field data_31 0x7ffe8b59ed10
Address of Field data_32 0x7ffe8b59ed18
Address of obj 0x7ffe8b59ecc8, object size 64 bytes
Address of Field data_1 0x7ffe8b59ecd0
Address of Field data_2 0x7ffe8b59ecd4
Address of Field data_3 0x7ffe8b59ecd8
Address of Field data_4 0x7ffe8b59ece0
Address of Field data_5 0x7ffe8b59ece4
Address of Field data_6 0x7ffe8b59ece8
Address of base obj 0x7ffe8b59ed08, object size 24 bytes
Address of Field data_31 0x7ffe8b59ed10
Address of Field data_32 0x7ffe8b59ed18
Address of Field data_10 0x7ffe8b59ecf0
Address of Field data_11 0x7ffe8b59ecf8
Address of Field data_12 0x7ffe8b59ed00
Object2::funcG()
vmem_func 0x55b311c2c96b start 2
Object2::funcG()
vmem_func end
vmem_func 0x55b311c317c8 start 3

As you can see from this output:

  • Compared with the previous code that does not use virtual inheritance, the object size of the lowest class in the inheritance hierarchy is Object2equal to the sum of the sizes of its two direct subclasses, and Object2its own member variables are treated as if they do not occupy any memory space. The contents of the top-level parent class of virtual inheritance are counted in the size of each class object that inherits it, but it only needs to be calculated once when calculating the size of the lowest-level class object;
  • Object2The memory layout of the class object is: all the contents of the first subclass (excluding the contents of the virtual inherited class, only the virtual function table pointer and data member variables of this parent class) -> followed by the second subclass All contents (excluding the contents of the virtual inherited class, including only the parent's virtual function table pointer and data member variables) -> the data member variables of the lowest subclass in the inheritance hierarchy (its virtual function table and its Shared by the first parent class) -> The content of the virtual inherited class is first the pointer of the virtual function table, and then each data member.

The memory layout of the Object2 class object here is as shown in the figure below. The arrow on the left points in the direction of increasing memory addresses. The memory layout of C++ class objects has nothing to do with whether the object is allocated on the heap or the stack.

 Low   |                                  |          
   |   |----------------------------------| <------ Object2 class object memory layout
   |   |           Object::vptr.          |---------|
   |   |----------------------------------|         |---------> |----------------------------|
   |   |     int32_t Object:: data_1      |                     |    Object2::~Object2()     |
  \|/  |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_2      |                     |    Object2::~Object2()     |
       |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_3      |                     |      Object::funcB()       |
       |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_4      |                     |      Object::funcC()       |
       |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_5      |                     |      Object2::funcG()      |
       |----------------------------------|                     |----------------------------|
       |     int32_t Object:: data_6      |                     |      Object2::funcF()      |
       |----------------------------------|                     |----------------------------|
       |          Object1::vptr           |---------|
       |----------------------------------|         |
       |     int32_t Object1:: data_1     |         |---------> |----------------------------|
       |----------------------------------|                     |    Object2::~Object2()     |
       |     int32_t Object1:: data_2     |                     |----------------------------|
       |----------------------------------|                     |    Object2::~Object2()     |
       |     int32_t Object1:: data_3     |                     |----------------------------|
       |----------------------------------|                     |      Object1::funcD()      |
       |     int32_t Object1:: data_4     |                     |----------------------------|
       |----------------------------------|                     |      Object1::funcE()      |
       |     int32_t Object1:: data_5     |                     |----------------------------|
       |----------------------------------|                     |      Object2::funcG()      |
       |     int32_t Object1:: data_6     |                     |----------------------------|
       |----------------------------------|
       |    int64_t Object2:: data_10     |
       |----------------------------------|
       |    int64_t Object2:: data_11     |         |---------> |----------------------------|
       |----------------------------------|         |           |    Object2::~Object2()     |
       |    int64_t Object2:: data_12     |         |           |----------------------------|
       |----------------------------------|         |           |    Object2::~Object2()     |
       |         ObjectBase::vptr         |---------|           |----------------------------|
       |----------------------------------|                     |      Object2::funcG()      |
       |   int64_t ObjectBase:: data_31   |                     |----------------------------|
       |----------------------------------|
       |   int64_t ObjectBase:: data_31   |
-------|----------------------------------|------------
       |                o                 |
       |                o                 |
       |                o                 |
       |                                  |
       |                                  |

Reference documentation

C++ virtual inheritance and virtual base classes

Memory Layout of C++ Object in Different Scenarios

memory layout C++ objects [closed]

Guess you like

Origin blog.csdn.net/tq08g2z/article/details/125033324