Memory distribution details when C++ class has virtual function of virtual function table

  • 2020-05-10 18:32:48
  • OfStack

Virtual function table

Anyone familiar with C++ should know that virtual functions (Virtual Function) are implemented through a table of virtual functions (Virtual Table). V-Table for short. In this table, the main is the address table of virtual functions of a class. This table solves the problem of inheritance and overwriting, ensuring that it can reflect the actual functions. In this way, in the instance of a class with virtual functions, the table is allocated to the memory of the instance. Therefore, when we manipulate a subclass with the pointer of the parent class, the table of virtual functions is very important. It is like a map 1, indicating the actual function to be called.

So let's focus on 1 for this table of virtual functions. The compiler for C++ is supposed to ensure that the pointer to the vtable exists at the top of the object instance (this is to ensure maximum performance when fetching to the vtable -- in the case of multiple inheritance or multiple inheritance). This means that we get this table of virtual functions from the address of the object instance, and then we can walk through the function pointer and call the corresponding function.

After all that I've said, I can tell you're probably more dazed than ever. Never mind, the following is a practical example, I believe you smart 1 see understand.

Suppose we have a class that looks like this:


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, 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 1 Function address: " << (int*)*(int*)(&b) << endl;

      // Invoke the first virtual function 

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

      pFun();

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

Virtual function table address: 0012FED4

Virtual function table - the first function address: 0044F148

Base::f

From this example, we can see that we can get the address of the vtable by forcibly converting &b to int *, and then, once again, we can get the address of the first vtable, Base::f(), which was verified in the above program (int* was forcibly converted to a function pointer). With this example, we 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 get the idea, don't you?

(int*)(&b) is the address of the pointer to the virtual function table

(int*)*(int*)(&b) is equal to the value of the virtual table pointer, which is the address of the virtual function table (the virtual function table should be equivalent to an array of Pointers).

*((int*)*(int*)(&b)) is the address of the first virtual function

Note: in the above figure, I added a node to the end of the vtable, which is the end of the vtable, just like the end of the string "/0" 1, which marks the end of the vtable. The value of this closing flag varies from compiler to compiler. Under WinXP+VS2003, this value is NULL. For Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3, this value is if 1, which means there is another vtable, and if 0, which means there is the last vtable.

Next, I'll show you what the vtable looks like for "no coverage" and "covered" respectively. A virtual function that does not override a parent class is meaningless. The main reason I want to talk about the case of no coverage is to give a comparison. In comparison, we can get a clearer picture of its internal implementation.

1 general inheritance (no virtual function coverage)

Next, let's take a look at what the inherited vtable looks like. Suppose there is an inheritance relationship as follows:

Note that in this inheritance relationship, the subclass does not override any of the parent functions. So, in an instance of a derived class, its virtual function table looks like this:

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

We can see the following:

1) virtual functions are placed in the table in the order in which they are declared.

2) the virtual function of the parent class precedes the virtual function of the subclass.

I believe the smart you 1 must be able to refer to the previous program, to write a paragraph of procedures to verify.

1 general inheritance (with virtual function overrides)

Overriding the virtual function of the parent class is an obvious thing to do; otherwise, virtual functions become meaningless. Now, let's take a look at 1, what happens if a subclass has a virtual function that overrides a virtual function of its parent class? Let's say we have an inheritance relationship like this.

In order to show the effect of inheritance, in this class design, I only covered one function of the parent class: f(). Then, for an instance of a derived class, its virtual function table would look like this:

As we can see from the table,

1) the overridden f() function is placed in the location of the original superclass virtual function in the virtual table.

2) functions that are not covered remain the same.

So we can see that for a program like this,

Base *b = new Derive();

b- > f();

The location of f() in the in-memory vtable referred to by b has been replaced by the Derive::f() function address, so that when the actual call occurs, Derive::f() is called. So you have polymorphism.

Multiple inheritance (no virtual function override)

Now, let's take a look at multiple inheritance, assuming that there is an inheritance relationship between one of the following classes. Note: the subclass does not override the parent function.

For a virtual function table in a 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 determined in the order of declaration)

This is done so that different parent types of Pointers to the same subclass instance can be called into the actual function.

Multiple inheritance (with virtual function overrides)
Now let's look at what happens if we have virtual function coverage.

In the following figure, we override the parent class's f() function in the subclass.

Here is the diagram for the virtual function table in the subclass instance:

We can see that the position of f() in the three parent vtable is replaced by the function pointer of the subclass. This way, we can point to the subclass with any 1 statically typed parent and call f() of the subclass. 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()

security

Every time I write C++, I can't help but criticize C++. This article is no exception. Through the above description, I believe we have a more detailed understanding of the table of virtual functions. Water can carry a boat or overturn it. Now, let's see what we can do with the vtable.

1. Access the subclass's own virtual functions through a pointer to the parent type

We know that a subclass that does not override a virtual function of its parent class is a meaningless thing. Because polymorphism is also based on function overloading. Although we can see in the diagram above that Base1 has Derive's virtual function in its virtual table, it is impossible to call the subclass's own virtual function with the following statement:

Base1 *b1 = new Derive();

b1- > f1 (); // compilation error

Any attempt to use a superclass pointer to call a member function in a subclass that does not override the superclass is considered illegal by the compiler, so such a program will not compile at all. At runtime, however, we can access the vtable as a pointer to violate C++ semantics. (you can do this by reading the code in the appendix below.)

2. Access the virtual function of non-public

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 virtual functions by accessing the virtual function table, which is very 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();

}

Appendix 1: view the virtual function table in VC

We can expand instances of the class in the VC IDE environment in the Debug state to see the vtable (not quite complete)

Appendix 2: routines

Here is a routine about multiple inherited vtable access:


#include <iostream>

using namespace std;

 

class Base1 {

public:

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

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

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

 

};

 

class Base2 {

public:

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

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

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

};

 

class Base3 {

public:

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

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

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

};

 

 

class Derive : public Base1, public Base2, public Base3 {

public:

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

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

};

 

 

typedef void(*Fun)(void);

 

int main()

{

      Fun pFun = NULL;

 

      Derive d;

      int** pVtab = (int**)&d;

 

      //Base1's vtable

      //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);

      pFun = (Fun)pVtab[0][0];

      pFun();

 

      //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);

      pFun = (Fun)pVtab[0][1];

      pFun();

 

      //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);

      pFun = (Fun)pVtab[0][2];

      pFun();

 

      //Derive's vtable

      //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);

      pFun = (Fun)pVtab[0][3];

      pFun();

 

      //The tail of the vtable

      pFun = (Fun)pVtab[0][4];

      cout<<pFun<<endl;

 

 

      //Base2's vtable

      //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

      pFun = (Fun)pVtab[1][0];

      pFun();

 

      //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

      pFun = (Fun)pVtab[1][1];

      pFun();

 

      pFun = (Fun)pVtab[1][2];

      pFun();

 

      //The tail of the vtable

      pFun = (Fun)pVtab[1][3];

      cout<<pFun<<endl;

 

      //Base3's vtable

      //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

      pFun = (Fun)pVtab[2][0];

      pFun();

 

      //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

      pFun = (Fun)pVtab[2][1];

      pFun();

 

      pFun = (Fun)pVtab[2][2];

      pFun();

 

      //The tail of the vtable

      pFun = (Fun)pVtab[2][3];

      cout<<pFun<<endl;

 

      return 0;

}

Related articles: