C++-virtual function table

Added some own understanding to the content of the original text, and the test compilation environment is win10+vs2015

Preface

The function of virtual function in C++ is mainly to realize the mechanism of polymorphism. Regarding polymorphism, in short, it is to use the pointer of the parent type to point to the instance of its subclass, and then call the member function of the actual subclass through the pointer of the parent class. This technique allows the pointer of the parent class to have "multiple forms", which is a generic technique. The so-called generic technology, to put it bluntly, is to try to use unchanging code to implement variable algorithms. For example: template technology, RTTI technology (Run-Time Type Identification), virtual function technology, either try to achieve resolution at compile time, or try to achieve resolution at runtime.

Virtual function table

Anyone who knows C++ should know that Virtual Function is implemented through a Virtual Table. Referred to as V-Table. In this table, it is mainly the address table of the virtual function of a class. This table solves the problems of inheritance and coverage and ensures that its content truly reflects the actual function. In this way, in the instance of a class with virtual functions, this table is allocated in the memory of this instance. Therefore, when we use the pointer of the parent class to manipulate a subclass, this virtual function table is particularly important. , It is like a map, indicating the actual function that should be called.

Here we focus on this virtual function table. The C++ compiler should ensure that the pointer of the virtual function table exists in the first position in the object instance (this is to ensure that the virtual function table has the highest performance-if there is multiple inheritance or multiple inheritance) ). This means that we get this virtual function table through the address of the object instance, and then we can traverse the function pointers in it and call the corresponding function.

Suppose we have such a class:

class Base {
 
     public:
 
            virtual void f() { cout << "Base::f" << endl; }
 
            virtual void g() { cout << "Base::g" << endl; }
 
            virtual void h() { cout << "Base::h" << endl; }
 
 
};

According to the above statement, we can get the virtual function table through the instance of Base. Here is the actual routine:

typedef void(*Fun)(void);
 
Base b;
 
Fun pFun = NULL;
 
cout << "虚函数表地址:" << (int*)*(int*)(&b) << endl;
 
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)*(int*)(&b) << endl;
 
 
// Invoke(调用) the first virtual function 
 
pFun = (Fun)*((int*)*(int*)(&b));
 
pFun();

The actual running results are as follows: (windows10+vs2015)

The debugging results are as follows:

The comparison found that the address of the virtual function table is the same as the address of the first function in the virtual function table.

Through this example, we can see that we can obtain the address of the virtual function table address by forcibly converting &b to int *, and then the address of the virtual function table can be obtained by taking the address, and the first address can be obtained by taking the address again. A virtual function is also the address of Base::f(), which is verified in the above program (int* is forced to be converted into a function pointer). Through this example, we can know that if you want to call Base::g() and Base::h(), the code is as follows

  (Fun)*((int*)*(int*)(&b)+0);  // Base::f()
 
  (Fun)*((int*)*(int*)(&b)+1);  // Base::g()
 
  (Fun)*((int*)*(int*)(&b)+2);  // Base::h()

You should understand this time. what? Still a little dizzy. Also, such code looks too messy. No problem, let me draw a picture to explain. As follows:

Note: In the above figure, I added an extra node at the end of the virtual function table, which is the end node of the virtual function table, just like the end character'\0' of the string, which marks the virtual function table The end. The value of this end flag is different under different compilers. Under WinXP+VS2003, this value is NULL. Under Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3, if this value is 1, it means there is another virtual function table, if the value is 0, it means it is the last virtual function table (this part is the original author’s test the result of).

General inheritance (no virtual function coverage)

Next, let's take a look at what the virtual function table looks like during inheritance. Suppose there is an inheritance relationship as shown below:

Please note that in this inheritance relationship, the subclass does not overload any functions of the parent class. Then, in the instance of the derived class, its virtual function table is as follows:

For instance: Derive d; The virtual function table is as follows:

We can see the following points:

1) The virtual functions are placed in the table in the order of their declaration.

2) The virtual function of the parent class is before the virtual function of the child class.

A concrete example:

#include <iostream>
using namespace std;

class Base {

public:

	virtual void f() { cout << "Base::f" << endl; }

	virtual void g() { cout << "Base::g" << endl; }

	virtual void h() { cout << "Base::h" << endl; }
};

class Derive : public Base 
{
public:
	virtual void f1() { cout << "Derive::f1" << endl; }

	virtual void g1() { cout << "Derive::g1" << endl; }

	virtual void h1() { cout << "Derive::h1" << endl; }
};

typedef void(*Fun)(void);

int main()
{
	Base b;
	Derive d;


	system("pause");
	return 0;
}

The breakpoint view of the virtual function table addresses of the parent class and subclass are as follows:

In the debugging state, only the virtual function table containing the parent class can be seen in the virtual function table of the subclass, and the addresses are the same.

 General inheritance (covered by virtual functions)

 It is obvious to override the virtual function of the parent class, otherwise, the virtual function becomes meaningless. Next, let's take a look, if there is a virtual function in the subclass that overrides the virtual function of the parent class, what will it look like? Suppose we have an inheritance relationship like the following.

 

In order to let everyone see the effect of being inherited, in the design of this class, I only covered one function of the parent class: f(). Then, for an instance of a derived class, its virtual function table will look like the following:

 We can see the following points from the table,

1) The overwritten f() function is placed in the position of the original parent virtual function in the virtual table.

2) The uncovered functions remain the same.

In this way, we can see that for the following program,

    Base *b = new Derive();
 
    b->f(); 

The f() position of the virtual function table in the memory pointed to by b has been replaced by the Derive::f() function address, so when the actual call occurs, Derive::f() is called. This achieves polymorphism.

Multiple inheritance (no virtual function coverage)

Next, let us take a look at the situation in multiple inheritance, suppose there is an inheritance relationship of the following class. Note: The subclass does not cover the function of the parent class.

For the virtual function table in the subclass instance, it looks like this:

We can see that:

1)   Each parent class has its own virtual table.

2)   The member functions of the subclass are placed in the table of the first parent class. (The so-called first parent class is judged according to the order of declaration)

This is done to solve the problem that the pointers of different parent types point to the same subclass instance, and the actual function can be called.

Multiple inheritance (with virtual function coverage)

Let's take a look again, if a virtual function coverage occurs.

In the figure below, we have covered the f() function of the parent class in the subclass.

 

 The following is a diagram of the virtual function table in the subclass instance:

We can see that the position of f() in the virtual function table of the three parent classes is replaced with the function pointer of the child class. In this way, we can point to the subclass from any parent class of static type and call the subclass's f(). Such as:

            Derive d;
 
 
            Base1 *b1 = &d;
 
 
            Base2 *b2 = &d;
 
 
            Base3 *b3 = &d;
 
 
            b1->f(); //Derive::f()
 
 
            b2->f(); //Derive::f()
 
 
            b3->f(); //Derive::f()
 
 
            b1->g(); //Base1::g()
 
 
            b2->g(); //Base2::g()
 
 
            b3->g(); //Base3::g()

safety

1. Access the subclass's own virtual function through the pointer of the supertype

We know that it is meaningless for the subclass not to overload the virtual function of the parent class. Because polymorphism is also based on function overloading. Although in the above figure we can see that there are Derive virtual functions in the virtual table of Base1, it is impossible for us to use the following statement to call the subclass's own virtual function:    

 Base1 *b1 = new Derive();
 
 b1->f1();  //编译出错

Any attempt to use the pointer of the parent class to call the member function of the subclass without covering the parent class will be regarded as illegal by the compiler. Therefore, such a program cannot be compiled at all. But at runtime, we can access the virtual function table through pointers to achieve behavior that violates C++ semantics. (About this attempt, by reading the code in the appendix below, I believe you can do this)

Two, access non-public virtual functions

In addition, if the virtual function of the parent class is private or protected, these non-public virtual functions will also exist in the virtual function table, so we can also access these non-public ones by accessing the virtual function table. Virtual functions, this is easy to do. 

class Base {
    private:
            virtual void f() { cout << "Base::f" << endl; } 
};
 
 
class Derive : public Base{ 
 
};
 
typedef void(*Fun)(void);
 
void main() {
 
    Derive d;
 
    Fun  pFun = (Fun)*((int*)*(int*)(&d)+0);
 
    pFun();
 
}

appendix

If you want to view the complete table of virtual functions, follow the steps below: VS2015 as an example

Open the "VS2015 Developer Command Prompt" above

Use the "/d1 reportAllClassLayout or reportSingleClassLayoutXXX" option of the cl command. The reportAllClassLayout option here will print a lot of related class information, which is generally not very useful. The XXX of the reportSingleClassLayoutXXX option represents the name of the class in the code to be compiled (here XXX class), and prints the memory layout and virtual function table of the XXX class (if there is no corresponding class in the code, the option is invalid).

My source file is test.cpp, so enter:

cl /d1 reportSingleClassLayoutDerive test.cpp

The output is as follows (corresponding here):

 

The corresponding examples are as follows:

#include <iostream>
using namespace std;

class Base {

public:

	virtual void f() { cout << "Base::f" << endl; }

	virtual void g() { cout << "Base::g" << endl; }

	virtual void h() { cout << "Base::h" << endl; }
};

class Derive : public Base 
{
public:
	virtual void f() { cout << "Derive::f1" << endl; }  //重载了父类的函数

	virtual void g1() { cout << "Derive::g1" << endl; }

	virtual void h1() { cout << "Derive::h1" << endl; }
};

typedef void(*Fun)(void);

int main()
{
	Base b;
	Derive d;

	b.f();
	d.f();

	Base* b1 = new Derive;
	b1->f();

	system("pause");
	return 0;
}

It can be seen that the thunk technology is used ( thunk technology is: the slot in the virtual function table still continues to put a virtual function entity address, but if this virtual function needs to be adjusted, the address in the slot points to a thunk and It is not the address of a virtual function entity. )

Personal summary

1) A virtual function table exists only when a class contains virtual functions. Objects belonging to the same class share the virtual function table, but have their own vptr (virtual function table pointer), and of course the address pointed to (the first address of the virtual function table) the same.

2) A virtual function in the parent class is equivalent to a virtual function in the child class. In other words, if there is a virtual function table in the parent class, there must be a virtual function table in the child class. Because you inherit the parent class. Some people also think that if the virtual function of the parent class is removed from the subclass, will these functions no longer be virtual functions? As long as it is a virtual function in the parent class, then even if virtual is not written in the subclass, it is still a virtual function. But no matter whether it is a parent class or a subclass, there will only be one virtual function table. It cannot be considered that there is a virtual function table in the subclass + a virtual function table in the parent class. A conclusion is drawn: there are two virtual functions in the subclass. table. Is it possible to have multiple virtual function tables in a subclass? We will explain this later;

3) If there is no new virtual function in the subclass at all, then we can consider the content of the virtual function table of the subclass and the virtual function table of the parent class to be the same. However, only the content is the same, the two virtual function tables are in different locations in the memory, in other words, these are two tables with the same content. Each item in the virtual function table stores the first address of a virtual function, but if an item in the virtual function table of the child class and an item in the virtual function table of the parent class represent the same function (this means that the child class does not cover the parent class Virtual function), the address of the function executed by the entry should be the same.

4) Part of the content beyond the virtual function table is unknown;

Summary: The virtual function table follows the class, and the virtual function table pointer follows the specific object.

Guess you like

Origin blog.csdn.net/www_dong/article/details/115047775