The article I saw in the morning is also reproduced now. The following is reprinted from:
Http://blog.csdn.net/dongfengsun/archive/2007/02/05/1502916.aspx
Address: http://www.cppblog.com/fwxjj/archive/2007/01/25/17996.html
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;
- 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;
- 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;
- Foo (B );
- Foo ();
- }
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 calledLate binding)OrRuntime binding)Technology.
Creating a virtual function with the virtual keyword can cause late bundling. The Compiler completes the necessary mechanism for late bundling.It creates a table (called vtable) for each class containing virtual functions, used to place the address of the virtual function.. In each class that contains virtual functions, the compiler secretly places a class calledVpointer (vptr)Pointer to the vtable of this object. Therefore, no matter how many virtual functions are contained in this object, the compiler only places 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 (for example, a, or something else ). This does not matter, because the vptr positions are the same, 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 that vptr points to 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 (),
If it is 1, it corresponds to fun2 (). Getfun ()
The returned 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
The unsigned char pointer calculates the offset, and the obtained result is re-input getp (). This operation should be the correct position of the function body.
Can it work correctly? Modify main () to test:
- Int main (){
- Base * P = new;
- 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.