Interview Series C + + object layout

Time:2021-1-25

We all know that C + + polymorphism is realized through the virtual function table. What is the specific type? Do you know? At the beginning, several questions are still raised

  • What is the layout of common class objects?
  • What is the layout of class objects with virtual functions?
  • What is the layout of class objects without covering functions under single inheritance?
  • What is the layout of class objects with covering functions under single inheritance?
  • What is the layout of class objects without covering functions under multiple inheritance?
  • What is the layout of class objects with covering functions under multiple inheritance?
  • Is the layout of class objects generated by different inheritance orders the same in multiple inheritance?
  • What is the layout of virtual inherited class objects?
  • What is the layout of class objects under diamond inheritance?
  • Why introduce virtual inheritance?
  • Why are there two destructors in the virtual function table?
  • Why can’t constructors be virtual functions?
  • Why do base class destructors need to be virtual?

To answer these questions, we need to know what polymorphism is.

What is polymorphism

Polymorphism can be divided into compile time polymorphism and run time polymorphism.

  • Compile time polymorphism: Based on template and function overloading, the behavior of objects has been determined at compile time, also known as static binding.
  • Runtime polymorphism: one of the characteristics of object-oriented, which enables the program to determine the corresponding calling method at runtime through inheritance, also known as dynamic binding. Its implementation mainly depends on the legendary virtual function table.

How to view the layout of objects

In GCC, you can use the following commands to view the object layout:

g++ -fdump-class-hierarchy  model.cc View the generated file after

In clang, you can use the following commands:

clang -Xclang -fdump-record-layouts -stdlib=libc++ -c model.cc
//View object layout
clang -Xclang -fdump-vtable-layouts -stdlib=libc++ -c model.cc
//View virtual function table layout

In fact, the above two ways are enough. You can also use GDB to view the memory layout. Here you can see the related references at the end of the article. This article is to use clang to view the object layout.

Next, let’s explore the layout of class objects under various inheritance conditions~

1. Layout of common objects

The code is as follows:

struct Base {
    Base() = default;
    ~Base() = default;
    
    void Func() {}

    int a;
    int b;
};

int main() {
    Base a;
    return 0; 
}

//Using clang - xclang - fdump record layouts - stdlib = libc + + - C model.cc see

The output is as follows:

*** Dumping AST Record Layout
         0 | struct Base
         0 |   int a
         4 |   int b
           | [sizeof=8, dsize=8, align=4,
           |  nvsize=8, nvalign=4]

*** Dumping IRgen Record Layout

The drawing is as follows:

Interview Series C + + object layout

It can be seen from the results that the size of the common structure base is 8 bytes, with a occupying 4 bytes and B occupying 4 bytes.

2. Layout of class objects with virtual functions

struct Base {
    Base() = default;
    virtual ~Base() = default;
    
    void FuncA() {}

    virtual void FuncB() {
        printf("FuncB\n");
    }

    int a;
    int b;
};

int main() {
    Base a;
    return 0; 
}

//Here you can see the layout of the object and the corresponding virtual function table
clang -Xclang -fdump-record-layouts -stdlib=libc++ -c model.cc
clang -Xclang -fdump-vtable-layouts -stdlib=libc++ -c model.cc

The object layout is as follows:

*** Dumping AST Record Layout
         0 | struct Base
         0 |   (Base vtable pointer)
         8 |   int a
        12 |   int b
           | [sizeof=16, dsize=16, align=8,
           |  nvsize=16, nvalign=8]

*** Dumping IRgen Record Layout

This structure containing virtual function has a size of 16. In the head of the object, the first 8 bytes are the pointers to the virtual function table, and the corresponding function pointer address to the virtual function. A takes 4 bytes, B takes 4 bytes, and the total size is 16.

Virtual function table layout:

Vtable for 'Base' (5 entries).
   0 | offset_to_top (0)
   1 | Base RTTI
       -- (Base, 0) vtable address --
   2 | Base::~Base() [complete]
   3 | Base::~Base() [deleting]
   4 | void Base::FuncB()

Draw the object layout as follows:

Interview Series C + + object layout

Let’s explore the following legendary virtual function table:

offset_to_top(0): indicates the offset of the current virtual function table address from the top address of the object. Because the head of the object is the pointer to the virtual function table, the offset is 0.

RTTI pointer: point to store runtime type information (type)_ Info) for runtime type identification, typeID and dynamic_ cast。

RTTI is the following virtual function table pointer to the real address, store all the virtual functions in the class, as for why there are two destructors, you can first focus on the layout of the object, the following will introduce.

3. The layout of class objects without covering function under single inheritance

struct Base {
    Base() = default;
    virtual ~Base() = default;
    
    void FuncA() {}

    virtual void FuncB() {
        printf("Base FuncB\n");
    }

    int a;
    int b;
};

struct Derive : public Base{
};

int main() {
    Base a;
    Derive d;
    return 0; 
}

Subclass object layout:

*** Dumping AST Record Layout
         0 | struct Derive
         0 |   struct Base (primary base)
         0 |     (Base vtable pointer)
         8 |     int a
        12 |     int b
           | [sizeof=16, dsize=16, align=8,
           |  nvsize=16, nvalign=8]

*** Dumping IRgen Record Layout

Same as above, the structure containing virtual function has a size of 16. In the head of the object, the first 8 bytes are pointers to the virtual function table, and the corresponding function pointer address of the virtual function. A takes 4 bytes, B takes 4 bytes, and the total size is 16.

Subclass virtual function table layout:

Vtable for 'Derive' (5 entries).
   0 | offset_to_top (0)
   1 | Derive RTTI
       -- (Base, 0) vtable address --
       -- (Derive, 0) vtable address --
   2 | Derive::~Derive() [complete]
   3 | Derive::~Derive() [deleting]
   4 | void Base::FuncB()

The drawing is as follows:

Interview Series C + + object layout

This is the same as the above. Note that the funcb function in the virtual function table is still the funcb function in the base class, because this function is not overridden in the subclass. If the subclass rewrites this function, what is the layout of the object? Please continue to look.

4. Layout of class objects with covering function under single inheritance

struct Base {
    Base() = default;
    virtual ~Base() = default;
    
    void FuncA() {}

    virtual void FuncB() {
        printf("Base FuncB\n");
    }

    int a;
    int b;
};

struct Derive : public Base{
    void FuncB() override {
        printf("Derive FuncB \n");
    }
};

int main() {
    Base a;
    Derive d;
    return 0; 
}

Subclass object layout:

*** Dumping AST Record Layout
         0 | struct Derive
         0 |   struct Base (primary base)
         0 |     (Base vtable pointer)
         8 |     int a
        12 |     int b
           | [sizeof=16, dsize=16, align=8,
           |  nvsize=16, nvalign=8]

*** Dumping IRgen Record Layout

It is the same as above. The structure containing virtual function has a size of 16. In the head of the object, the first 8 bytes are the pointers to the virtual function table, and the corresponding function pointer address to the virtual function. A takes 4 bytes, B takes 4 bytes, and the total size is 16.

Subclass virtual function table layout:

Vtable for 'Derive' (5 entries).
   0 | offset_to_top (0)
   1 | Derive RTTI
       -- (Base, 0) vtable address --
       -- (Derive, 0) vtable address --
   2 | Derive::~Derive() [complete]
   3 | Derive::~Derive() [deleting]
   4 | void Derive::FuncB()

Interview Series C + + object layout

Note that the funcb function in the virtual function table is already the funcb function in derive, because the function of the parent class is overridden in the subclass.

Note that there are two terms in RTTI here, which means that the virtual table addresses of base and derive are the same. The virtual functions in base class and derive class are under this chain. Here, we can continue to pay attention to the following multiple inheritance cases to see what the differences are.

5. Layout of class objects without covering function under multiple inheritance

struct BaseA {
    BaseA() = default;
    virtual ~BaseA() = default;
    
    void FuncA() {}

    virtual void FuncB() {
        printf("BaseA FuncB\n");
    }

    int a;
    int b;
};

struct BaseB {
    BaseB() = default;
    virtual ~BaseB() = default;
    
    void FuncA() {}

    virtual void FuncC() {
        printf("BaseB FuncC\n");
    }

    int a;
    int b;
};

struct Derive : public BaseA, public BaseB{
};

int main() {
    BaseA a;
    Derive d;
    return 0; 
}

Class object layout:

*** Dumping AST Record Layout
         0 | struct Derive
         0 |   struct BaseA (primary base)
         0 |     (BaseA vtable pointer)
         8 |     int a
        12 |     int b
        16 |   struct BaseB (base)
        16 |     (BaseB vtable pointer)
        24 |     int a
       28 |     int b
          | [sizeof=32, dsize=32, align=8,
          |  nvsize=32, nvalign=8]

The size of derive is 32. Note that there are two virtual table pointers here. Because derive is multi inheritance, it generally inherits several classes with virtual functions, and there are several virtual table pointers in the object layout, and the subclasses also inherit the data of the base class,Generally speaking, regardless of memory alignment, the size of the subclass (inheriting the parent class) = the size of the subclass (not inheriting the parent class) + the size of all the parent classes

Virtual function table layout:

Vtable for 'Derive' (10 entries).
   0 | offset_to_top (0)
   1 | Derive RTTI
       -- (BaseA, 0) vtable address --
       -- (Derive, 0) vtable address --
   2 | Derive::~Derive() [complete]
   3 | Derive::~Derive() [deleting]
   4 | void BaseA::FuncB()
   5 | offset_to_top (-16)
   6 | Derive RTTI
       -- (BaseB, 16) vtable address --
   7 | Derive::~Derive() [complete]
       [this adjustment: -16 non-virtual]
   8 | Derive::~Derive() [deleting]
       [this adjustment: -16 non-virtual]
   9 | void BaseB::FuncC()

The object layout can be drawn as follows:

Interview Series C + + object layout

offset_to_top(0): represents the offset of the current address of the virtual function table (base, derive) from the top address of the object. Because the head of the object is the pointer of the virtual function table, the offset is 0.

Pay more attention to thisRTTIThe virtual table addresses of basea and derive are the same. The virtual functions in basea class and derive class are under this chain, up to offset_ to_ Top (- 16) is the virtual function table of basea and derive.

offset_to_top(-16): indicates the offset of the address of the current virtual function table (base) from the top address of the object. Because the head of the object is the pointer of the virtual function table, the offset is – 16. This is used for this pointer offset, which will be introduced in the next section.

Note the following RTTI: there is only one item, which represents the virtual function table of the base, and there are also two virtual destructors behind it. Why are there four destructors of derive class, and how to call them? Please continue to look~

6. Layout of class objects with covering function under multiple inheritance

struct BaseA {
    BaseA() = default;
    virtual ~BaseA() = default;
    
    void FuncA() {}

    virtual void FuncB() {
        printf("BaseA FuncB\n");
    }

    int a;
    int b;
};

struct BaseB {
    BaseB() = default;
    virtual ~BaseB() = default;
    
    void FuncA() {}

    virtual void FuncC() {
        printf("BaseB FuncC\n");
    }

    int a;
    int b;
};

struct Derive : public BaseA, public BaseB{
    void FuncB() override {
        printf("Derive FuncB \n");
    }

    void FuncC() override {
        printf("Derive FuncC \n");
    }
};

int main() {
    BaseA a;
    Derive d;
    return 0; 
}

Object layout:

*** Dumping AST Record Layout
         0 | struct Derive
         0 |   struct BaseA (primary base)
         0 |     (BaseA vtable pointer)
         8 |     int a
        12 |     int b
        16 |   struct BaseB (base)
        16 |     (BaseB vtable pointer)
        24 |     int a
        28 |     int b
           | [sizeof=32, dsize=32, align=8,
           |  nvsize=32, nvalign=8]

*** Dumping IRgen Record Layout

The class size is still 32, the same as above.

Virtual function table layout:

Vtable for 'Derive' (11 entries).
   0 | offset_to_top (0)
   1 | Derive RTTI
       -- (BaseA, 0) vtable address --
       -- (Derive, 0) vtable address --
   2 | Derive::~Derive() [complete]
   3 | Derive::~Derive() [deleting]
   4 | void Derive::FuncB()
   5 | void Derive::FuncC()
   6 | offset_to_top (-16)
   7 | Derive RTTI
       -- (BaseB, 16) vtable address --
   8 | Derive::~Derive() [complete]
       [this adjustment: -16 non-virtual]
   9 | Derive::~Derive() [deleting]
       [this adjustment: -16 non-virtual]
  10 | void Derive::FuncC()
       [this adjustment: -16 non-virtual]

Interview Series C + + object layout

offset_to_top(0): represents the offset of the current address of the virtual function table (base, derive) from the top address of the object. Because the head of the object is the pointer of the virtual function table, the offset is 0.

Pay more attention to thisRTTIThere are two items in, which indicate that the virtual table addresses of basea and derive are the same. The virtual functions in basea class and the virtual functions in derive class are under this chain, up tooffset_to_top(-16)Previously, it was the virtual function table of basea and derive.

offset_to_top(-16): represents the offset of the current virtual function table (base) address from the top address of the object. Because the head of the object is the pointer to the virtual function table, the offset is – 16. When the reference or pointer of the base class, base, actually accepts objects of derived type. When base > funcc() is executed, because funcc() has been overridden, and this pointer points to objects of base type, this pointer needs to be adjusted, that isoffset_to_top(-16)Therefore, the this pointer adjusts 16 bytes upwards. After calling FuncC (), it calls to the FuncC () function in the Derive virtual function table after being rewritten. These functions marked with adjustment need pointer adjustment. As for the above-mentioned virtual function here is how to call, I guess you also understand it~

7. Is the layout of class objects the same due to different inheritance order of multiple inheritance?

struct BaseA {
    BaseA() = default;
    virtual ~BaseA() = default;
    
    void FuncA() {}

    virtual void FuncB() {
        printf("BaseA FuncB\n");
    }

    int a;
    int b;
};

struct BaseB {
    BaseB() = default;
    virtual ~BaseB() = default;
    
    void FuncA() {}

    virtual void FuncC() {
        printf("BaseB FuncC\n");
    }

    int a;
    int b;
};

struct Derive : public BaseB, public BaseA{
    void FuncB() override {
        printf("Derive FuncB \n");
    }

    void FuncC() override {
        printf("Derive FuncC \n");
    }
};

int main() {
    BaseA a;
    Derive d;
    return 0; 
}

Object layout:

*** Dumping AST Record Layout
         0 | struct Derive
         0 |   struct BaseB (primary base)
         0 |     (BaseB vtable pointer)
         8 |     int a
        12 |     int b
        16 |   struct BaseA (base)
        16 |     (BaseA vtable pointer)
        24 |     int a
        28 |     int b
           | [sizeof=32, dsize=32, align=8,
           |  nvsize=32, nvalign=8]

*** Dumping IRgen Record Layout

It can be seen here that the object layout is different from that above. The virtual function table pointer and data of baseb are on the top, and the virtual function table pointer and data of basea are on the bottom. Inherit in the order of a and B. the layout of the object is that a is on the top and B is on the bottom. Inherit in the order of B and a. the layout of the object is that B is on the top and a is on the bottom.

Virtual function table layout:

Vtable for 'Derive' (11 entries).
   0 | offset_to_top (0)
   1 | Derive RTTI
       -- (BaseB, 0) vtable address --
       -- (Derive, 0) vtable address --
   2 | Derive::~Derive() [complete]
   3 | Derive::~Derive() [deleting]
   4 | void Derive::FuncC()
   5 | void Derive::FuncB()
   6 | offset_to_top (-16)
   7 | Derive RTTI
       -- (BaseA, 16) vtable address --
   8 | Derive::~Derive() [complete]
       [this adjustment: -16 non-virtual]
   9 | Derive::~Derive() [deleting]
       [this adjustment: -16 non-virtual]
  10 | void Derive::FuncB()
       [this adjustment: -16 non-virtual]

The object layout is as follows:

Interview Series C + + object layout

The layout of virtual function table is also different. Baseb and derive share the same virtual table address, which is above the whole virtual table layout, while the lower part of the layout is the virtual table of basea. It can be seen that the virtual table layout of subclasses is different with different inheritance order.

8. The layout of virtual inheritance

struct Base {
    Base() = default;
    virtual ~Base() = default;
    
    void FuncA() {}

    virtual void FuncB() {
        printf("BaseA FuncB\n");
    }

    int a;
    int b;
};

struct Derive : virtual public Base{
    void FuncB() override {
        printf("Derive FuncB \n");
    }
};

int main() {
    Base a;
    Derive d;
    return 0; 
}

Object layout:

*** Dumping AST Record Layout
         0 | struct Derive
         0 |   (Derive vtable pointer)
         8 |   struct Base (virtual base)
         8 |     (Base vtable pointer)
        16 |     int a
        20 |     int b
           | [sizeof=24, dsize=24, align=8,
           |  nvsize=8, nvalign=8]

*** Dumping IRgen Record Layout

Under virtual inheritance, the object layout here is different from that of ordinary single inheritance. The subclass and base class share a virtual table address in ordinary single inheritance. Under virtual inheritance, the subclass and virtual base class have a pointer to the virtual table address respectively. The total size of the two pointers is 16, plus the size of a and B is 8, which is 24.

Virtual function table:

Vtable for 'Derive' (13 entries).
   0 | vbase_offset (8)
   1 | offset_to_top (0)
   2 | Derive RTTI
       -- (Derive, 0) vtable address --
   3 | void Derive::FuncB()
   4 | Derive::~Derive() [complete]
   5 | Derive::~Derive() [deleting]
   6 | vcall_offset (-8)
   7 | vcall_offset (-8)
   8 | offset_to_top (-8)
   9 | Derive RTTI
       -- (Base, 8) vtable address --
  10 | Derive::~Derive() [complete]
       [this adjustment: 0 non-virtual, -24 vcall offset offset]
  11 | Derive::~Derive() [deleting]
       [this adjustment: 0 non-virtual, -24 vcall offset offset]
  12 | void Derive::FuncB()
       [this adjustment: 0 non-virtual, -32 vcall offset offset]

The object layout is as follows:

Interview Series C + + object layout

vbase_offset(8): the offset of the object in the object layout from the pointer address to the virtual function table of the virtual base class

vcall_offset(-8): whenvirtual base classThe reference or pointer of base actually accepts objects of derive type. When base > funcb() is executed, because funcb() has been rewritten, this pointer points to objects of base type. This pointer needs to be adjusted, that is, Vcall_ Offset (-8), so the this pointer adjusts 8 bytes upwards. After calling FuncB (), it calls to the rewritten FuncB () function.

9. Virtual inheritance of object layout with uncovered functions

struct Base {
    Base() = default;
    virtual ~Base() = default;
    
    void FuncA() {}

    virtual void FuncB() {
        printf("Base FuncB\n");
    }

    virtual void FuncC() {
        printf("Base FuncC\n");
    }

    int a;
    int b;
};

struct Derive : virtual public Base{
    void FuncB() override {
        printf("Derive FuncB \n");
    }
};

int main() {
    Base a;
    Derive d;
    return 0; 
}

Object layout:

*** Dumping AST Record Layout
         0 | struct Derive
         0 |   (Derive vtable pointer)
         8 |   struct Base (virtual base)
         8 |     (Base vtable pointer)
        16 |     int a
        20 |     int b
           | [sizeof=24, dsize=24, align=8,
           |  nvsize=8, nvalign=8]

*** Dumping IRgen Record Layout

As in the case of virtual inheritance above, ordinary single inheritance subclass and base class share a virtual table address, while in the case of virtual inheritance, subclass and virtual base class respectively have a pointer to the virtual table address. The sum of the two pointers is 16, plus the size of a and B is 8, which is 24.

Virtual function table layout:

Vtable for 'Derive' (15 entries).
   0 | vbase_offset (8)
   1 | offset_to_top (0)
   2 | Derive RTTI
       -- (Derive, 0) vtable address --
   3 | void Derive::FuncB()
   4 | Derive::~Derive() [complete]
   5 | Derive::~Derive() [deleting]
   6 | vcall_offset (0)
   7 | vcall_offset (-8)
   8 | vcall_offset (-8)
   9 | offset_to_top (-8)
  10 | Derive RTTI
       -- (Base, 8) vtable address --
  11 | Derive::~Derive() [complete]
       [this adjustment: 0 non-virtual, -24 vcall offset offset]
  12 | Derive::~Derive() [deleting]
       [this adjustment: 0 non-virtual, -24 vcall offset offset]
  13 | void Derive::FuncB()
       [this adjustment: 0 non-virtual, -32 vcall offset offset]
  14 | void Base::FuncC()

The object layout is as follows:

Interview Series C + + object layout

vbase_offset(8): the offset of the object in the object layout from the pointer address to the virtual function table of the virtual base class

vcall_offset(-8): whenvirtual base classThe reference or pointer of base actually accepts objects of derive type. When base > funcb() is executed, because funcb() has been rewritten, this pointer points to objects of base type. This pointer needs to be adjusted, that is, Vcall_ Offset (-8), so the this pointer adjusts 8 bytes upwards. After calling FuncB (), it calls to the rewritten FuncB () function.

vcall_offset(0): when the base reference or pointer base actually accepts objects of derived type, when base > funcc() is executed, because funcc() is not overridden, there is no need to adjust this pointer, that is, Vcall_ Offset (0), then call FuncC ().

10. The layout of class objects under diamond inheritance

struct Base {
    Base() = default;
    virtual ~Base() = default;
    
    void FuncA() {}

    virtual void FuncB() {
        printf("BaseA FuncB\n");
    }

    int a;
    int b;
};

struct BaseA : virtual public Base {
    BaseA() = default;
    virtual ~BaseA() = default;
    
    void FuncA() {}

    virtual void FuncB() {
        printf("BaseA FuncB\n");
    }

    int a;
    int b;
};

struct BaseB : virtual public Base {
    BaseB() = default;
    virtual ~BaseB() = default;
    
    void FuncA() {}

    virtual void FuncC() {
        printf("BaseB FuncC\n");
    }

    int a;
    int b;
};

struct Derive : public BaseB, public BaseA{
    void FuncB() override {
        printf("Derive FuncB \n");
    }

    void FuncC() override {
        printf("Derive FuncC \n");
    }
};

int main() {
    BaseA a;
    Derive d;
    return 0; 
}

Class object layout:

*** Dumping AST Record Layout
         0 | struct Derive
         0 |   struct BaseB (primary base)
         0 |     (BaseB vtable pointer)
         8 |     int a
        12 |     int b
        16 |   struct BaseA (base)
        16 |     (BaseA vtable pointer)
        24 |     int a
        28 |     int b
        32 |   struct Base (virtual base)
        32 |     (Base vtable pointer)
        40 |     int a
        44 |     int b
           | [sizeof=48, dsize=48, align=8,
           |  nvsize=32, nvalign=8]

*** Dumping IRgen Record Layout

The size is 48. There is no need to introduce it too much. I believe you already know.

Virtual function table:

Vtable for 'Derive' (20 entries).
   0 | vbase_offset (32)
   1 | offset_to_top (0)
   2 | Derive RTTI
       -- (BaseB, 0) vtable address --
       -- (Derive, 0) vtable address --
   3 | Derive::~Derive() [complete]
   4 | Derive::~Derive() [deleting]
   5 | void Derive::FuncC()
   6 | void Derive::FuncB()
   7 | vbase_offset (16)
   8 | offset_to_top (-16)
   9 | Derive RTTI
       -- (BaseA, 16) vtable address --
  10 | Derive::~Derive() [complete]
       [this adjustment: -16 non-virtual]
  11 | Derive::~Derive() [deleting]
       [this adjustment: -16 non-virtual]
  12 | void Derive::FuncB()
       [this adjustment: -16 non-virtual]
  13 | vcall_offset (-32)
  14 | vcall_offset (-32)
  15 | offset_to_top (-32)
  16 | Derive RTTI
       -- (Base, 32) vtable address --
  17 | Derive::~Derive() [complete]
       [this adjustment: 0 non-virtual, -24 vcall offset offset]
  18 | Derive::~Derive() [deleting]
       [this adjustment: 0 non-virtual, -24 vcall offset offset]
  19 | void Derive::FuncB()
       [this adjustment: 0 non-virtual, -32 vcall offset offset]

The object layout is as follows:

Interview Series C + + object layout

vbase_offset (32)

vbase_offset (16): the offset of the object in the object layout from the pointer address to the virtual function table of the virtual base class

offset_to_top (0)

offset_to_top (-16)

offset_to_top (-32): the offset between the address to the virtual function table and the address at the top of the object.

vcall_offset(-32): whenvirtual base classThe reference or pointer of base actually accepts objects of derive type. When base > funcb() is executed, because funcb() has been rewritten, this pointer points to objects of base type. This pointer needs to be adjusted, that is, Vcall_ Offset (-32), so the this pointer adjusts 32 bytes upwards. After calling FuncB (), it calls to the rewritten FuncB () function.

Why should we inherit falsely

As shown in the figure:

Interview Series C + + object layout

In the case of non virtual inheritance, it is obvious that D will inherit a twice and store two copies of a’s data internally, which wastes space and has ambiguity. When D calls a’s method, because there are two A’s, the compiler does not know which a’s method to call, which a’s method will report an error. Therefore, with virtual inheritance, the problem of space waste and ambiguity is solved. Under virtual inheritance, only one shared base class sub object is inherited, no matter how many times the base class appears in the derived hierarchy. Shared base class sub objects are called virtual base classes. Under virtual inheritance, the duplication of sub objects of base class and the resulting ambiguity are eliminated.

Why are there two destructors in the virtual function table

In the previous code output, we can see that there are two destructors in the virtual function table, one is marked as deleting, and the other is marked as complete. Because there are two kinds of construction methods for objects, stack construction and heap construction, there are also two kinds of construction methods for objects in the corresponding implementation. The difference between the construction of objects on the stack and that of objects on the stack is that the construction of stack memory does not need to be done To execute the delete function, it is automatically recycled.

Why can’t constructors be virtual functions.

The purpose of the constructor is to determine the type of the object and allocate space for the object in the compilation phase. If there is a virtual function in the class, the virtual function table will be initialized in the constructor, but the execution of the virtual function depends on the virtual function table. If the constructor is a virtual function, it needs to rely on the virtual function table to execute, and only in the constructor can the virtual function table be initialized. The problem of laying eggs and laying hens is very contradictory, so the constructor cannot be a virtual function.

Why is a base class destructor a virtual function.

Generally, the destructor of the base class should be set as a virtual function, because if it is not set as a virtual function, only the destructor of the base class will be called, not the destructor of the subclass, which may lead to memory leakage.

Summary

offset_to_top: the offset of the object from the top address of the object in the object layout.

RTTI pointer: point to store runtime type information (type)_ Info) for runtime type identification, typeID and dynamic_ cast。

vbase_offset: the offset of the object in the object layout from the pointer address to the virtual function table of the virtual base class.

vcall_offset: the parent class reference or pointer points to the subclass object. When calling the method overridden by the subclass, it is used to adjust the pointer address of the virtual function, so as to call the overridden method successfully.

thunk: indicates that the function call with adjustment field in the above virtual function table needs to adjust this pointer before it can call the function overridden by the subclass.

Finally, we summarize the layout of objects in Linux through two diagrams

A * a = new derive(); // A is the base class of derive

As shown in the figure:

Interview Series C + + object layout

A is stored in the stack as an object pointer, pointing to the instance memory of class A in the heap. There are virtual function table pointers in the instance memory layout, the virtual function table pointed by the pointer is stored in the data segment, and the functions pointed by each function pointer in the virtual function table are in the code segment.

Interview Series C + + object layout

The virtual table structure is roughly as shown in the figure above. The normal virtual table structure contains the last three items. When there is virtual inheritance, there will be the first two items.

reference material:

https://www.cnblogs.com/qg-wh…

https://blog.csdn.net/fuzhong…

https://zhuanlan.zhihu.com/p/…

https://mp.weixin.qq.com/s/sq…

https://jacktang816.github.io…

https://blog.mengy.org/cpp-vi…

https://blog.mengy.org/cpp-vi…

https://blog.mengy.org/extend…

https://www.zhihu.com/questio…

https://www.zhihu.com/questio…

https://zhuanlan.zhihu.com/p/…

https://wizardforcel.gitbooks…

https://www.cnblogs.com/xhb19…

https://www.lagou.com/lgeduar…

Interview Series C + + object layout

More articles, please pay attention to my v x Princess number: program meow adult, welcome to exchange.