Virtual function, deep analysis of virtual table

Virtual function, deep analysis of virtual table

Object-oriented, starting with a single class.

class A
{
private:
    int m_a;
    int m_b;
};
 

There are two member variables in this class, both of which are inttypes, so how much memory space does this class occupy in memory?

sizeof(A), 8 bytes, one intoccupies four bytes. The following figure verifies:

How are these two data arranged in memory?

It turns out that this is the case. We draw the structure diagram of the a object in the memory according to the debug address.

If class Aincluded in the member function? Size A, and how much?

class A
{
public:
    void func1() {}    
private:
    int m_a;
    int m_b;
};
 

Tell you the answer directly, how big is the member function of the class? No one can answer you, and it is not the focus of this article. The member functions of the class are placed in the code area and not counted in the size of the class.

The objects of the class share this piece of code. Just imagine, if every object has a piece of code, how much space does it take to store this code? So objects of the same class share a piece of code.

How to distinguish different objects when sharing the same piece of code?

In fact, when you call a member function, it a.func1()will be translated by the compiler A::func1(&a), that is A* const this, this is the address of the a object.

Therefore, the corresponding data can be found according to the this pointer, and different data can be processed through the same piece of code.

Next, we discuss inheritance. The child class inherits the parent class, will inherit the data of the parent class, and the call rights of the parent class function.

The following test can verify this situation.

class A
{
public:
    void func1() { cout << "A func1" << endl; }
private:
    int m_a;
    int m_b;
};

class B : public A
{
public:
    void func2() { cout << "B func2" << endl; }
private:
    int m_c;
};

int main(int argc, char const* argv[])
{
    B b;
    b.func1();
    b.func2();
    return 0;
}
 

Output:

//A func1
//B func2
 

So what is the structure of object b in memory?

Inheritance relationship, first inherit the data in a, and then have a copy of its own data.

Every class that contains virtual functions has a virtual table. The virtual table belongs to the class, not to a specific object. A class only needs one virtual table. All objects of the same class use the same virtual table.

In order to specify the virtual table of the object, the object contains a pointer to a virtual table to point to the virtual table used by itself. In order to make each object of the class containing the virtual table have a virtual table pointer, the compiler adds a pointer to the class *__vptrto point to the virtual table. In this way, when the object of the class is created, it has this pointer, and the value of this pointer is automatically set to point to the virtual table of the class.

class A
{
public:
    void func1() { cout << "A func1" << endl; }
    virtual void vfunc1() { cout << "A vfunc1" << endl; }
private:
    int m_a;
    int m_b;
};
 

cout << sizeof(A);, Output 12, A includes two int type member variables, a virtual pointer, the pointer occupies 4 bytes.

The memory structure of a is as follows:

The virtual table is an array of function pointers, and all function pointers stored in the array point to the location of the virtual function.

When an object calls a virtual function, it will find the position of the virtual table according to the virtual pointer, find the position of the virtual function in the array according to the order of the virtual function declaration, find the address of the virtual function, and then call the virtual function.

Calling ordinary functions is not like this. Ordinary functions are assigned the function location in the compilation stage and can be called directly.

class A
{
public:
    void func1() { cout << "A func1" << endl; }
    virtual void vfunc1() { cout << "A vfunc1" << endl; }
private:
    int m_a;
    int m_b;
};

class B : public A
{
public:
    void func1() { cout << "B func1" << endl; }
    virtual void vfunc2() { cout << "B vfunc2" << endl; }
private:
    int m_a;
};
 

Like this, class B inherits from class A, and a virtual function vfunc2 is defined in B. What about its virtual table?

Given a conclusion, the virtual table is shown in the following figure:

Let's verify it:

A a; B b; void(*avfunc1)() = (void(*)()) *(int*) (*(int*)&a); void (*bvfunc1)() = (void(*)()) *(int*) *((int*)&b); void (*bvfunc2)() = (void(*)()) * (int*)(*((int*)&b) + 4); avfunc1(); bvfunc1(); bvfunc2();

To explain the code: void(*avfunc1)()declare a return value void, a function pointer avfunc1 with no parameters, and the variable name represents that we want to take the virtual function vfunc1 of class A.

The first part of the right half means that (void(*)())we will finally convert to a pointer corresponding to the above-mentioned type, and an address needs to be given to the right.

Let's see (*int(*)&a), the address of a is forcibly converted to int*, and then dereferenced to get the address of the virtual pointer.

*(int*) (*(int*)&a) Then force dereference to get the address of the virtual table, and finally force it to a function pointer.

In the same way, bvfunc1 and bvfunc2 +4are obtained, because a pointer occupies 4 bytes, and +4the second item of the virtual table is obtained.

cover

class A
{
public:
    void func1() { cout << "A func1" << endl; }
    virtual void vfunc1() { cout << "A vfunc1" << endl; }
private:
    int m_a;
    int m_b;
};

class B : public A
{
public:
    void func1() { cout << "B func1" << endl; }
    virtual void vfunc1() { cout << "B vfunc1" << endl; }
private:
    int m_a;
};
 

The subclass rewrites the virtual function of the parent class, and the function signature needs to be consistent. In this case, the structure in memory is:

Polymorphism

When the parent class pointer points to the child class object, if the pointer calls a virtual function, the compiler will find the corresponding address from the virtual function table pointed to by the virtual pointer and execute the corresponding function.

If there are many subclasses, each subclass covers the corresponding virtual function, then the virtual function found through the virtual table executes different codes after execution, it shows polymorphism.

We call the process of calling a virtual function through a virtual table as dynamic binding, and its manifestation is called runtime polymorphism. Dynamic binding is different from traditional function calls. Traditional function calls are called static binding, which means that the function call can be determined at the compilation stage.

So, when will the dynamic binding of the function be performed? This needs to meet the following three conditions.

  • Call function through pointer

  • Pointer upcast upcast (conversion from inherited class to base class is called upcast)

  • Virtual function

Why can the parent class pointer point to the child class?

The child class inherits from the parent class, and the child class also belongs to the type of A.

Finally, let's experience it through an example:

class Shape
{
public:
    virtual void draw() = 0;
};

class Rectangle : public Shape
{
    void draw() { cout << "rectangle" << endl; }
};

class Circle : public Shape
{
    void draw() { cout << "circle" << endl; }
};

class Triangle : public Shape
{
    void draw() { cout << "triangle" << endl; }
};


int main(int argc, char const *argv[])
{
    vector<Shape*> v;
    v.push_back(new Rectangle());
    v.push_back(new Circle());
    v.push_back(new Triangle());
    for (Shape* p : v) {
        p->draw();
    }
    return 0;
}
 

Some words are in the vernacular, haha, if this article is well written and solves your doubts, please like it before you go!

Please also point out the wrong place, and let everyone learn and make progress together.