Polymorphism is one of the basic features of object-oriented programming. In C , the polymorphism is achieved by virtual function. Let's take a simple code:
#include
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 a; foo (b); foo (a);} The result is:
Base :: fun1 ()
Base :: fun2 ()
Base :: fun3 ()
A :: fun1 ()
A :: fun2 ()
Base :: fun3 ()
Only by the base class interface, the program calls the correct function, it seems to know the same type of object we entered!
So how do the compiler know the correct code? In fact, the compiler does not know the correct position of the function body to be called, but it is inserted into the code that can find the correct function. This is called Late Bindling or Runtime Binding Technology.
Creating a virtual function via the Virtual keyword can trigger the evening bundle, the compiler has completed the necessary mechanisms for realizing the evening bundle after the scene. It creates a table (called vtable) for each class containing virtual functions, used to place the address of the virtual function. In each class containing virtual functions, the compiler is secretly placed a pointer called Vpointer, pointing to the VTABLE of this object. So no matter how many virtual functions are included, both the compiler can only place a VPTR. The VPTR is configured by the compiler in the constructor to complete the initialization, pointing to the corresponding VTABLE, so that the object "knows" what type it is. VPTR is in the same location of the object, often the beginning of the object. In this way, the compiler can easily find the object's VTABLE and get the address of the function.
If we use SizeOf to see the length of the previous Base class, we will find that its length is more than just an int, but it has added the length of a VOID pointer (in my machine, one INT accounts for 4 Bytes, one Void pointer accounts for 4 bytes, so that the length of BASE is 8 bytes).
Whenever you create a class containing virtual functions or a class that is derived from a class containing virtual functions, the compiler creates a unique VTable for this class. In vtable, the address of all virtual functions in this class or in its base class is placed, so the order of virtual functions can be easily found by the offset can be found. If there is no overriding in the derived class, the address of this virtual function (as shown in the above program results) is also used. So far, everything goes well. Below, our test begins.
As far as I know, we can try to call the virtual function through your own code, that is to say, we have to find the footprint of the code that can find the correct letter of the code. If we have an Base pointer as an interface, it must point to a base or object derived from base, or A, or anything else. This is irrelevant, because the location of VPTR is the same, generally in the beginning of the object. If this is the case, then the pointer of the object of the virtual function, such as the Base pointer, the point to which is the other pointer-VPTR. Vptr Pointing VTable is actually an array of function pointers. Now, VPTR is pointing to its first element, which is a function pointer. If the VPTR is offset by one Void pointer, then it should point to the second function pointer in the vTable.
This seems like a chain of a pointer, we have to get the next pointer it pointing from the current pointer, so that we can "obey the vine". So, I will introduce a function:
Void * getp (void * p) {return (void *) * (unsigned long *) p;}
We don't consider it beautiful or not, we just trial. GetP () can get the next pointer it pointing from the current pointer. If we can find the address of the function, use what to store it? I want to use a function pointer:
TYPEDEF VOID (* FUN) ();
It is similar to the three virtual functions in Base. For simple matter, we don't want any input and return, we just know that it is actually executed.
Then, we are responsible for the "touch melon" function debut:
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 Base or the pointer to the Base derived object. The second parameter is VTable offset, and if it is 0, if it is 0, if it is 1 corresponding FUN2 (). GetFun () returns the FUN type function pointer, the one defined above. It can be seen that the function first calls getP (), which has obtained the vptr pointer, then uses a UNSIGNED CHAR pointer to operate the offset, and the result is entered again, this time it is It is the correct position of the function.
So can it work correctly? We 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;
Exhaust moments come, let us run it!
Run results: a :: fun1 () a :: fun2 () base :: fun3 ()
At this point, we really succeed. Through our method, we have obtained the VPTR of the object, and its virtual function is performed in its in vitro.