Xie lingbing, a fellow of students, asked a question. Run the following code to delete some irrelevant content.
Code:
- # Include <iostream>
- Using namespace STD;
- Class base
- {
- Public:
- Virtual void funb1 ()
- {
- Cout <"funb1 base called." <Endl;
- }
- Void funb2 ()
- {
- Cout <"funb2 base called." <Endl;
- }
- };
- Class der: public Base
- {
- Public:
- Void funb1 ()
- {
- Cout <"funb1 Dev called." <Endl;
- }
- Virtual // In my opinion, this virtual should be removed
- Void funb2 ()
- {
- Cout <"funb2 Dev called." <Endl;
- }
- };
- Int main ()
- {
- Base B;
- Der * PDER = (der *) & B; // converts a base class object to a derived class object.
- PDER-> funb1 ();
- PDER-> funb2 ();
- Return 0;
- }
This is a question. What can I ask? I didn't see it. I guess: "What will happen to the above program ?"
It would be really tiring to answer this question!
I would like to give you the following points for comprehensive consideration, but the first point is complaints. We recommend that you skip this step.
1. Forcibly convert a base class object to a derived class. In the C ++ standard, the behavior is "undefined/undefined". What is the most terrible problem in C ++? Is the most terrible undefined behavior.
So, if you have to say that this is a "pen test", you have to write it out. What is this question? A lot of people may think that the problem is more important than the code. If this is my pen question, my reserved answer is three words. If anyone can write "undefined" on the answer, it means that he usually reads many c ++ books. This is my second-class answer. The first-class answer is: according to common compilers and versions, different results are provided in detail. This is naturally something that the normal people can do.
2. Speaking of standards, let's talk about reality,
"In reality, who will write such unreasonable code ?" This is what you said, not necessarily. Oh, it's a project you haven't run ...... To be honest, in C ++, I feel that this type of usage is relatively small, but in Delphi, especially delphi.net, there are actually a lot of such usage. It is sometimes called the "helper" class. Why? We still use C ++ for analysis.
2.1First, delete the "virtual" in front of funb2 in Row 3 and der class --- I think you may have copied it wrong? The reason will be explained later.
2.2Let's talk about the concept of "virtual table" first. First, let's look at base. It has no member data, so it seems that it can be considered a C-language favorite pod, but is it actually a pod? In other words, can you directly pass a base object to a pure C function? Of course not, because it contains virtual functions, and virtual functions insert a "virtual function address jump table/vtable" in the object ".
This is where the single virtual function mechanism of C ++ is criticized: Only space can be sacrificed to win time. Unlike languages like Delphi, you can choose whether to take space or time, in this case, the C ++ standard is not wronged, because it does not specify how to implement virtual functions. Fortunately, many compilers have chosen the vtable method that we are currently familiar with. In addition, they chose to sacrifice space and win time. That is, they all put vtable addresses in objects, in this way, you only need to add an offset once to obtain a virtual function after you have an object address (such as this) in your hand. The method to save time and space is to put the virtual table in the class, and then keep a pointer to the class for the object. For C ++, if you do not want to violate the C ++ standard daily records, then, we had to find a way to secretly add static data to the base class, just like what MFC did. This is another question.
2.3Now we know that base actually has data. Insert a sentence: if the base actually has a member data, will the virtual table data be placed before or after the member data? This is too important, but it is a pity that different C ++ compilers are different at this point! Why is there no Binary compatible interface for C ++ dynamic libraries with export classes? Why can't BC call C ++-style DLL written by VC? But can c-style DLL be used universally? This is part of the guilt.
Back to the topic, because the base class does not have member data, the vtab is in the front or back, and the result is the same (just one person is in the queue, do you say it is in the front or the end ?). But let's talk about it. Now, the common compiler, vtab, is placed in front of the actual Member Data. Note that I have said "normally ".
A virtual table is generally a pointer of an "array Pointer" (which is roughly the same without careful consideration). That is, an address is saved in the object and then directed to a table (array. We can do this experiment: Write a class with both virtual tables and member data:
Code:
- Struct coo
- {
- Int D; // put D at the beginning, but in fact there is a vtable in front of it
- Coo ()
- : D (100)
- {}
- Virtual void Foo ();
- };
- Void coo: Foo ()
- {}
- Int main ()
- {
- Coo O;
- Int * P = (int *) & O;
- Cout <* P <Endl
- <O. D <Endl;
- Return 0;
- }
If the COO: Foo function is not virtual, the screen output must be two 100 (that is, the value of D ). But now? No. * P is a big number, which is actually the memory address of the vtable.
After talking about it for a long time, it seems that you haven't mentioned the subject yet. Don't worry, it's coming soon: that is, when the program is running, the call of non-virtual member functions is directly addressing (call XXXX) and the call of virtual member functions, you need to first find the "vtable" address, and then find the real function address (call xxxx1 + xxxx2) from the table ). corresponding to the base class object in this question, the funb1 function needs to find the vtable and then jump, while funb2 directly jumps to the address on hand. Speaking of this, you should be able to think of the answer, so you don't have to look at it later. It will take a long time later.
2.4As mentioned above, vtab is actually a pointer pointing to an array, and each element in the array is a function address. Well, we can make "hack" more thorough for CoO class objects:
Code:
- Coo O;
- Int * P = (int *) & O;
- Int ADDR = * (int *) (* P );
- Cout <ADDR <Endl
- <O. D <Endl;
Now, the ADDR output on the screen is actually the address of Coo: Foo. If you do not believe it, you can define a function pointer and then add the ADDR (after forced conversion) assign a value to it, and then call the function pointer to execute it (as a job ). You said it is impossible, because there is no this pointer, how do I call a member function? It doesn't matter. It cannot die. As long as the foo function does not touch the member data, it is no different from a static member function. What if Members are used in Foo? You can also add an additional coo * parameter when defining the function pointer:
Code:
- Typedef void (* pfunc) (COO *);
At the end of this article, I will give a complete answer to this question.
2.5Next, we will discuss the vtable problem of the derived class. Simply put:
First, it completely copies the virtual table of the base class, just like deriving common member data. (Many compiler authors once again sang: Our goal is to save time for free space ).
Suppose we have a CoO2 class which is derived from COO, but it does not add any new data. (Here, any member data and virtual tables), you can perform the following tests on the aforementioned Code:
Code:
- Struct CoO2: Public coo
- {
- };
- CoO2 O2;
- P = (int *) (& O2 );
- ADDR = * (int *) (* P );
- Cout <ADDR <Endl;
The ADDR output again is exactly the same as the ADDR output earlier. They are all coo: Foo addresses!
(P is to forcibly convert the o address into an integer pointer, ADDR is to point to the content of P, and then forcibly treat it as an integer pointer, and then retrieve the content pointed to by the pointer, is the function address)
Second, if the derived class is a virtual function of the base class, it will be implemented again. For example, if der is in base, the following relationship exists:
Base virtual table:
[Base: funb1 address]
Der's virtual table:
[Der: funb1 address] [base: funb1 address]
It can be seen that in the DER virtual table, der: funb1 occupies the address of the base class function with the same name! This is the answer.
See this line of code:
Code:
- Der * PDER = (der *) & B;
PDER is forced to "think" as a derived class, but is this forced conversion possible to change the fact that B is actually a base object? Is it possible that the memory layout of B has changed? Of course it is impossible. Next, we will put the real memory layout of * PDER's virtual table together with the memory layout "mistaken:
Real virtual table: [base: funb1 address]
Empty table: [der: funb1 address] [base: funb1 address]
When calling: PDER-> funb1 (); funb1 is a virtual function, you can find it in the virtual table. It finds the "[der :: funb1 address] ", it is very happy, but in fact the number it finds is" [base: funb1 address] ". So of course it is to call the function of the base class.
Do you want to write it down? There are still a lot to say, for example:
What if I write a function in der and then it calls Base: funb1? A: It will die! Why?
If the virtual modifier der: funb2 is not removed from the original question, we will die, but why?
What if base contains member data? What if the derived class also contains member data? Think about it as a job.
Finally, how can this approach be used? Just playing cool in the test?
If you have a class definition and implementation Library but no source code, is there a way to access its private members (data or functions) outside the class ), there must be a solution for specific compilers! That's how to get the object address, and then forcibly convert it to make it accessible.
Okay. Is it to force access to private data? Not necessarily. If you answer the first three questions correctly, you can know that we can use this method to extend a class-isn't it "derived" to extend a class? Of course, it is derived, but sometimes it cannot be derived ~~~ Because an object has been generated in the original Class Library and the code of the object is new, we can no longer modify it. In this "invalid" requirement, c ++ programmers created this method and called it "hacker/hacker ". Isn't it mentioned earlier that in Delphi, is it called "helper/helper class? Why is the difference between the two languages so great? No way. Delphi programmers are usually kind (including pandatv's authors), while C ++ programmers are always black ".
Third, and finally, we will give a complete example of the "black" mentioned later:
Code:
- # Include <iostream>
- Using namespace STD;
- Class coo
- {
- Public:
- Coo ()
- : D (100)
- {}
- PRIVATE:
- Virtual void Foo (); // Private!
- Int D;
- };
- Void coo: Foo ()
- {
- Cout <D <"~~~!!!!~~~~ "<Endl;
- }
- Int main ()
- {
- Coo O;
- Int * P = (int *) (& O );
- Int ADDR = * (int *) (* P );
- Typedef void (* pfunc) (COO *);
- Pfunc = (pfunc) (ADDR );
- Pfunc (& O); // hack! Private member function called
- Return 0;
- }
When you see a question in the middle of the night, you can answer the question in the middle of the night. The code is simply compiled and tested using gcc, and no more compiler tests are available.
As mentioned in the vernacular C ++, it is "Child success" to learn the C ++ language well, including the criteria for familiarizing yourself with it. However, after analyzing this question, we will find that: answer this question, rather than relying on the "C ++ standard" knowledge, we need to understand the implementation behind C ++. I always think this is like "taking the door to the left", or sometimes it has special skill, but basically it should not be entangled by beginners. I specially read the original post of Xie and saw that he was trying to use the "C ++ standard" to explain it in a euphemism. He is making an "interesting" mistake: after knowing the "Answer", he starts to use the "standard" to set the "Answer" (this understanding of the standard, ).
-------------------------------------
If you want to communicate with me, click the following link to become a friend:
Http://student.csdn.net/invite.php? U= 112600 & C = f635b3cf130f350c