[C ++ in-depth analysis] 36. C ++ object model (below: memory distribution at inheritance, virtual function table)

In the last blog, we mentioned that class and struct follow the same memory alignment rules. The member functions and class variables in class are stored separately. The sizeof is used to solve the memory size of member variables. It does not include member functions. Member functions are in the code segment in.

How to calculate the size of the subclass after inheritance?

1 Inherited object model

  • Subclass is a superposition of the parent class member sub-class of new members get

Insert picture description here
The size of the subclass is the member variable of the parent class followed by the newly added member variable of the subclass, and then the size is calculated. Below we prove by programming experiment

Programming experiment: inherited object model

// 36-1.cpp
#include<iostream>
using namespace std;
class Demo
{
protected:
    int mi;
    int mj;
public:
    void print()
    {
        cout << "mi = " << mi << ", "
            << "mj = " << mj << endl;
    }
};
class Derived : public Demo
{
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    void print()
    {
        cout << "mi = " << mi << ", "
            << "mj = " << mj << ", "
            << "mk = " << mk << endl;
    }
private:
    int mk;
};
struct Test
{
    int mi;
    int mj;
    int mk;
};
int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
    Derived d(1, 2, 3);
    d.print();
    Test *p = reinterpret_cast<Test*>(&d);
    p->mi = 10;
    p->mj = 20;
    p->mk = 30;
    d.print();
    return 0;
}
  • The subclass is obtained by superimposing the new members of the subclass from the parent class members, so Derived superimposes mk on the basis of the Demo members of the class. When calculating the size of a class object, the size of the member variable is calculated, and the member function is saved in the code segment, not counting the object size. So the size of Demo is 8 bytes and Derived is 12 bytes.
  • The memory distribution of class Derived and struct Test is the same, you can use the Test pointer to point to the Derived object, so that this piece of memory is reinterpreted, and the variables of the class object can be modified.

Compile and run:

$ g++ 36-1.cpp -o 36-1
$ ./36-1
sizeof(Demo) = 8
sizeof(Derived) = 12
mi = 1, mj = 2, mk = 3
mi = 10, mj = 20, mk = 30

2 Polymorphic object model (virtual function)

2.1 The realization principle of C ++ polymorphism

  • When declaring virtual functions, the compiler generates a virtual function table ( data structure that stores the addresses of member functions ) in the class
  • When a virtual function is defined in the class, each object has a pointer to the virtual function table
  • The virtual function table is automatically generated and maintained by the compiler

As shown in the following figure: When defining a virtual function, in addition to the member variables, the object will also contain a pointer to the virtual function table, which stores a pointer to the member function.

Note: The pointer of the virtual function table exists in the foremost position in the object instance

Insert picture description here

2.2 Virtual function table

General inheritance (no virtual function coverage)
Let ’s take a look at the virtual function table during inheritance. Assume that there is an inheritance relationship as shown below:
Insert picture description here
Member functions are all virtual functions. In this inheritance relationship, the subclass does not overload any parent class The function. The virtual function table in the derived class is as follows:
Insert picture description here
we can see:

  • Virtual functions are placed in the table in the order in which they are declared
  • The virtual function of the parent class precedes the virtual function of the child class

General inheritance (with virtual function coverage)
If there is a virtual function in the subclass that overloads the virtual function of the parent class, what does the virtual function table look like? We have an inheritance relationship like the following.
Insert picture description here
In order to compare the effect after inheritance, only one function f () of the parent class is covered in the class. The virtual function table of the instance of the derived class is as follows:
Insert picture description here
you can see

  • The overlaid f () function is placed in the virtual table in the original parent virtual function
  • Functions that are not covered are still

The pointer to the virtual function table stored in the object is at the front of the memory, and the following experiments prove the existence and location of the pointer to the virtual function table in the object.

Programming experiment : pointer to virtual function table in object

We modify the above code, define the print () function in the Demo as a virtual function, add a pointer at the front of the structure struct Test, we prove that the memory of class Derived is the same as the memory of struct Test.

// 36-1.cpp
#include<iostream>
using namespace std;
class Demo
{
protected:
    int mi;
    int mj;
public:
    virtual void print()		// 虚函数,类对象中将有一个指针,指向虚函数表
    {
        cout << "mi = " << mi << ", "
            << "mj = " << mj << endl;
    }
};
class Derived : public Demo
{
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    void print()
    {
        cout << "mi = " << mi << ", "
            << "mj = " << mj << ", "
            << "mk = " << mk << endl;
    }
private:
    int mk;
};
struct Test				// 结构体中指针在最前面,和Derived内存布局相同
{
    void* p;
    int mi;
    int mj;
    int mk;
};
int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
    Derived d(1, 2, 3);
    d.print();
    Test *p = reinterpret_cast<Test*>(&d);
    p->mi = 10;
    p->mj = 20;
    p->mk = 30;
    d.print();
    return 0;
}

First calculate the size of the parent class demo and the subclass class Derived. The object contains a pointer to the virtual function table, the class size is 8 bytes, so the Demo size is 16, and the Derived size is 24.

The pointer to the virtual function table in the class object is at the front, and the memory layout is the same as that of struct Test. We can modify the member variables in the class through the pointer of the structure.

Compile and run:

$ g++ 36-1.cpp -o 36-1
$ ./36-1
sizeof(Demo) = 16
sizeof(Derived) = 24
mi = 1, mj = 2, mk = 3
mi = 10, mj = 20, mk = 30

The size of Demo is 16 bytes and the size of Derived is 24 bytes, which proves the existence of pointers in the class object. The data of the class object can be modified through the structure pointer, which proves that the memory distribution of the members in the class object is exactly the same as the structure Test.

2.3 The process of calling a virtual function

The process of calling the virtual function is shown in the figure below

The class object looks up the virtual function table according to the pointer. There is a function pointer pointing to the virtual function in the virtual function table, and the function can be found according to the function pointer.
Insert picture description here
If it is not a virtual function, the compiler can directly determine the address of the called member function. Therefore, virtual functions are less efficient than ordinary member functions .

3 C language realizes polymorphism

This part is difficult, and is used to understand the implementation process of polymorphism.

// Polymorphism1.h
#ifndef _POLYYMORPHISM_H_
#define _POLYYMORPHISM_H_
typedef void Demo;
typedef void Derived;

Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);

Derived* Derived_Create(int i, int j, int k);
int Derived_GetK(Derived* pThis);
int Derived_Add(Derived* pThis, int value);
#endif
// Polymorphism1.c
#include "Polymorphism.h"
#include "malloc.h"

static int Demo_Virtual_Add(Demo* pThis, int value);
static int Derived_Virtual_Add(Demo* pThis, int value);

struct VTable     // 2. 定义虚函数表数据结构
{
    int (*pAdd)(void*, int);   // 3. 虚函数表里面存储什么???
};
struct ClassDemo
{
    struct VTable* vptr;     // 1. 定义虚函数表指针  ==》 虚函数表指针类型???
    int mi;
    int mj;
};
struct ClassDerived
{
    struct ClassDemo d;
    int mk;
};

static struct VTable g_Demo_vtbl = 
{
    Demo_Virtual_Add
};
static struct VTable g_Derived_vtbl = 
{
    Derived_Virtual_Add
};

Demo* Demo_Create(int i, int j)
{
    struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo)); 
    if( ret != NULL )
    {
        ret->vptr = &g_Demo_vtbl;   // 4. 关联对象和虚函数表
        ret->mi = i;
        ret->mj = j;
    }
    return ret;
}

int Demo_GetI(Demo* pThis)
{
     struct ClassDemo* obj = (struct ClassDemo*)pThis;    
     return obj->mi;
}

int Demo_GetJ(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->mj;
}

// 6. 定义虚函数表中指针所指向的具体函数
static int Demo_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->mi + obj->mj + value;
}

// 5. 分析具体的虚函数!!!!
int Demo_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->vptr->pAdd(pThis, value);
}

void Demo_Free(Demo* pThis)
{
    free(pThis);
}

Derived* Derived_Create(int i, int j, int k)
{
    struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));   
    if( ret != NULL )
    {
        ret->d.vptr = &g_Derived_vtbl;
        ret->d.mi = i;
        ret->d.mj = j;
        ret->mk = k;
    }   
    return ret;
}

int Derived_GetK(Derived* pThis)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    return obj->mk;
}

static int Derived_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis; 
    return obj->mk + value;
}

int Derived_Add(Derived* pThis, int value)
{   
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    return obj->d.vptr->pAdd(pThis, value);
}
// 36-2.c
#include<iostream>
#include"Polymorphism.h"
using namespace std;
void run(Demo* p, int v)
{
	int r = Demo_Add(p, v);
	printf("r = %d\n", r);
}
int main()
{
    Demo* pb = Demo_Create(1, 2);
    Derived* pd = Derived_Create(1, 22, 333);
    
    printf("pb->add(3) = %d\n", Demo_Add(pb, 3));
    printf("pd->add(3) = %d\n", Derived_Add(pd, 3));
    
    run(pb, 3);
    run(pd, 3);
    Demo_Free(pb);
    Demo_Free(pd);
    return 0;
}
$ g++ 36-2.c Polymorphism1.c -o 36-2
$ ./36-2
pb->add(3) = 6
pd->add(3) = 336
r = 6
r = 336

4 Summary

1. The essence of inheritance is the superposition of member variables between father and son
2. C ++ polymorphism is realized through virtual function table (automatically maintained by the compiler)
3. The efficiency of virtual functions is lower than that of ordinary member functions

Published 298 original articles · praised 181 · 100,000+ views

Guess you like

Origin blog.csdn.net/happyjacob/article/details/104411231