C++ virtual function table analysis (transfer)

Reprint  C ++ virtual function tables parsing (transfer) - to write really good, could not help but turn to Favorites
function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}

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 plainly, is to try to use unchanging code to implement variable algorithms. For example: template technology, RTTI technology, virtual function technology, either try to achieve resolution at compile time, or try to achieve resolution at runtime.

Regarding the use of virtual functions, I will not elaborate here. You can look at related C++ books. In this article, I just want to give you a clear analysis from the implementation mechanism of virtual functions.

Of course, the same articles have appeared on the Internet, but I always feel that these articles are not easy to read. There are large sections of code, no pictures, no detailed instructions, no comparisons, and no analogy. It is not conducive to learning and reading, so this is the reason I want to write this article. I also hope that you can give me more opinions.

Closer to home, let us enter the world of virtual functions together.

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, the main one is 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, this table is allocated in the memory of this instance in the instance of a class with virtual functions, so when we use the pointer of the parent class to manipulate a subclass, this virtual function table appears to be important It is like a map, indicating the actual function that should be called.

Here we focus on this virtual function table. As mentioned in the C++ standard specification, the compiler must ensure that the pointer of the virtual function table exists in the first position in the object instance (this is to ensure that the offset of the virtual function is correctly obtained). This means that we get this virtual function table through the address of the object instance, and then we can traverse the function pointers and call the corresponding function.

After listening to me so much, I can feel that you may be more dizzy now than before. It doesn't matter, the following is a practical example, I believe you will understand it at a glance.

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 << "Virtual function table address:" << (int*)(&b) << endl;

cout << "Virtual function table — the first function address:" << (int*)*(int*)(&b) << endl;

// Invoke the first virtual function

pFun = (Fun)*((int*)*(int*)(&b));

pFun ();

The actual running results are as follows: (Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

Virtual function table address: 0012FED4

Virtual function table-the address of the first function: 0044F148

Base::f

Through this example, we can see that we can obtain the address of the virtual function table by forcibly converting &b to int *, and then, by taking the address again, we can get the address of the first virtual function, which is Base::f (), this is verified in the above program (forcing int* into a function pointer). Through this example, we can know that if we 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, this 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, this value is if 1, it means there is another virtual function table, if the value is 0, it means it is the last virtual function table.

Below, I will separately explain the appearance of the virtual function table when "without coverage" and "with coverage". Virtual functions that do not override the parent class are meaningless. The main reason why I want to describe the situation without coverage is to give a comparison. In comparison, we can more clearly know its internal implementation.

General inheritance (no virtual function coverage)

Next, let us 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.

I believe you can refer to the previous program to write a program for verification.

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? Assume that 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) Functions that are not covered remain.

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

Base *b = new Derive();

b->f();

The position of f() in 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 like the following. 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

Every time I write an article about C++, it is inevitable to criticize C++. This article is no exception. Through the above description, I believe we have a more detailed understanding of the virtual function table. Water can carry or overturn a boat. Next, let's take a look at what bad things we can do with virtual function tables.

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(); //Compile error

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. (For 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, but 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.

Such as:

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 ();

}

Concluding remarks

C++ is a magic language. For programmers, we never seem to know what the language is doing behind our backs. To be familiar with this language, we must understand the things in C++ and the dangerous things in C++. Otherwise, this is a programming language that shoots itself in the foot.

Guess you like

Origin blog.csdn.net/yuanzi2009/article/details/6465722