Polymorphism -- vptr -- vtable

Source: Internet
Author: User

Polymorphism is one of the basic features of object-oriented programming. In C ++, polymorphism is implemented through virtual functions. Let's look at a simple piece of code:
  

#include <iostream>
using namespace std;
class Base
{
int a;
public:
virtual void fun1() {
cout << "Base::fun1()" << endl;
}
virtual void fun2() {
cout << "Base::fun2()" << endl;
}
virtual void fun3() {
cout << "Base::fun3()" << endl;
}
};
class A: public Base
{
int a;
public:
void fun1() {
cout << "A::fun1()" << endl;
}
void fun2() {
cout << "A::fun2()" << endl;
}
};
void foo (Base &obj)
{
obj.fun1();
obj.fun2();
obj.fun3();
}
int main()
{
Base b;
A a;
foo(b);
foo(a);
}

The running result is:

  Base::fun1()  Base::fun2()  Base::fun3()  A::fun1()  A::fun2()  Base::fun3() 

Only through the interface of the base class, the program calls the correct function, it is like knowing the type of the input object!
So how does the compiler know where the correct code is located? In fact, the compiler does not know the correct position of the function body to be called during compilation, But it inserts a piece of code that can find the correct function body. This is called the late binding or runtime binding technology.
   Creating a virtual function with the virtual keyword can cause late bindingThe compiler completes the necessary mechanism for late bundling. It creates a table (vtable) for each class that contains virtual functions to place the address of the virtual function. In each class that contains virtual functions, the compiler secretly places a pointer called vpointer (abbreviated as vptr) pointing to the vtable of this object. Therefore, no matter how many virtual functions are contained in this object package, the compiler can only put one vptr. Vptr is initialized by the Code secretly inserted by the compiler in the constructor, pointing to the corresponding vtable, so that the object "knows" what type it is. Vptr is located at the same position of the object, often at the beginning of the object. In this way, the compiler can easily find the vtable of the object and obtain the address of the function body.
If we use sizeof to view the length of the base class, we will find that its length is not just the length of an int, it adds the length of a void pointer (in my machine, an int occupies 4 bytes, and a void pointer occupies 4 bytes, in this way, the length of the class base is 8 bytes ).
Whenever you create a class that contains a virtual function or derive a class from a class that contains a virtual function, the compiler creates a unique vtable for this class. In vtable, the addresses of all virtual functions in this class or its base class are placed. These virtual functions are in the same order, therefore, the offset can easily find the address of the required function body. If a virtual function in the base class is not overwritten in the derived class, the address of the virtual function of the base class is also used (as shown in the preceding program results ).

  

 

So far, everything went well. Next, we started the experiment.
As we have learned, we can try to call virtual functions through our own code. That is to say, we need to find the footprint of the code that the compiler secretly inserts to find the correct function body.
If we have a base pointer as an interface, it must point to a base or base-derived object, or a, or something else. This does not matter, because the positions of vptr are similar, generally at the beginning of the object. In this case, the pointer to an object containing a virtual function, such as a base pointer, points to another pointer, vptr.The vtable to which vptr points is actually an array of function pointers.Now, vptr is pointing to its first element, which is a function pointer. If vptr is offset by a void pointer, it should point to the second function pointer in vtable.
This seems like a chain of pointers. We have to get the next pointer from the current pointer so that we can "get it straight ". Let me introduce a function:

void *getp (void *p)
{
return (void *) * (unsigned long *)p;
}

We do not consider whether it is beautiful or not. We are just experimenting. Getp () obtains the next pointer from the current pointer. If we can find the address of the function body, what should we use to store it? I would like to use a function pointer:

typedef void (*fun)();

It is similar to the three virtual functions in the base. For simplicity, we do not need to input or return any input. We only need to know that it is actually executed.
Then, the function responsible for "Touch melon" was launched:
  

fun getfun (Base *obj, unsigned long off)
{
void *vptr = getp(obj);
unsigned char *p = (unsigned char *)vptr;
p += sizeof(void *) * off;
return (fun)getp(p);
}

The first parameter is the base pointer. We can enter the base or base-derived object pointer. The second parameter is the vtable offset. If the offset is 0, it corresponds to fun1 (), and if it is 1, it corresponds to fun2 (). Getfun () returns the function pointer of the fun type, which we defined above. As you can see, the function first calls getp () to the base pointer, obtains the vptr pointer, and then uses an unsigned char pointer to calculate the offset, enter getp () again in the obtained result. This time, we will get the correct position of the function body.
Can it work correctly? Modify main () to test:
  

int main()
{
Base *p = new A;
fun f = getfun(p, 0);
(*f)();
f = getfun(p, 1);
(*f)();
f = getfun(p, 2);
(*f)();
delete p;

}

The exciting moment is coming. Let's run it!
The running result is:
  

A::fun1()A::fun2()Base::fun3()

 
So far, we have succeeded. Through our method, we obtain the vptr of the object and execute its virtual function in its external body.

Virtual tables under multiple inheritance

First convert the address of the Multi-inheritance object to void * and then to the second base class, then call the nth function of the second base class. The result will call the nth function of the first base class.
Therefore, multiple inheritance relies entirely on the recognition and auxiliary compilation of the compiler. That isThe compiler completes the offset operation for the starting addresses of two vtables..

#include <iostream>
using namespace std;

class base2
{
public:
base2()
{
cout << "base2" << endl;
}
virtual std::string ff()
{
return "base2";
}
protected:
private:
};

class base
{
public:
base()
{
cout << "base" << endl;
}
virtual std::string f1()
{
return "base";
}
virtual std::string f2()
{
return "base";
}
virtual std::string f3()
{
return "base";
}
protected:
private:
};

class child : public base , public base2
{
public:
child()
{
cout << "child" << endl;
}
virtual std::string f2()
{
return "child";
}
virtual std::string f3()
{
return "child";
}
protected:
private:
};

class grant : public child
{
public:
grant()
{
cout << "grant" << endl;
}
virtual std::string f3()
{
return "grant";
}
protected:
private:
};

void main()
{
base a;
child b;
grant c;

base *p1 = &b;
base2 *p2 = (base2 *)(void *)&b;
base2 *p22 = (base2 *)&b;
}

The above memory structure shows that the pointer values obtained in different ways are different.

First, look at the memory structure of the Child class. We can find that there are two virtual table pointers in child, one pointing to base and the other pointing to base2. When function access is required, only the first position element is used as the virtual table pointer.

As the first base class pointer, P1 does not need to be offset. P2 performs pointer offset during normal pointer conversion. However, if the child type is discarded first, the compiler cannot perform the offset operation because it cannot identify the type, and an error occurs when accessing the function.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.