These two days, a C + + novice asked me a question, his project has a piece of code execution is not correct, do not know what the reason. I made a tune, and if the code is streamlined, it's probably the following:
classibasea{ Public: Virtual voidFnA () =0; intM_ntesta;};classibaseb{ Public: Virtual voidFnB () =0; intM_ntestb;};classCTest: PublicIbasea, Publicibaseb{ Public: Virtual voidFnA () {printf ("fna\n"); } Virtual voidFnB () {printf ("fnb\n"); }};int_tmain (intARGC, _tchar*argv[]) {CTest*ptest =NewCTest; void*p = (void*) PTest; Ibasea*pbasea = (ibasea*) p; Pbasea-FnA (); Ibaseb*pbaseb = (ibaseb*) p; Pbaseb-FnB (); Pbaseb= (ibaseb*) PTest; Pbaseb-FnB (); GetChar (); return 0;}
Perhaps the reader will find it strange that there is a void* conversion in the middle. This is not surprising, because this code is I put the most fundamental problem in his code after streamlining, because the combination of his code context framework design, in the middle is really so, just a glance seems easy to ignore. In fact, just a simple debugging will find that the pointer variable Pbaseb in fact and Pbasea is exactly the same, and debugging found its virtual table address is the same, but if the writing is not the same.
Pbaseb = (ibaseb*) pTest;
So how did this difference come about? This is going to start with a pointer conversion from C + + multiple inheritance.
In fact, C + + internal pointer conversion is a common thing, such as unsigned number to signed number conversion, C + + typical will report a warning, if it is set to the highest level or even direct error. Sub-class pointers to the parent class pointer, because C + + multiple inheritance use of the occasion is not too much, so most of the time the direct conversion can be, even according to the above conversion method is not a problem. Because C + + pointer conversion is simply the original object's address according to the new type to parse it.
However, this simple conversion has a little-known pit for multiple inheritance of C + +. For the above code, the object memory layout generated by the CTest class might look like this:
Ibasea----------- > |
_vfptr |
|
M_ntesta |
Ibaseb----------- > |
_vfptr |
|
M_ntestb |
If it is converted to Ibasea, then directly ptest the memory address of the first address, in accordance with Ibasea resolution can be, so say Pbasea->fna ();
But for ibaseb *pbaseb = (ibaseb*) p, in fact, Ptest's memory header address is parsed directly according to Ibasea. From the memory layout, the first one is mistaken for the IBASEB address. Instead of executing PBASEB->FNB (), this statement actually takes the first function address in the virtual table and calls it directly. Since two virtual function definitions are consistent, there is no problem, otherwise it will collapse directly.
From disassembly we can also see that the entire execution process is to directly assign p to Pbaseb, and then take the first 4 bytes of Pbaseb, that is, the virtual table address, and then take the virtual table address of the first 4 bytes, that is, the address of a virtual function. Then, starting from the 008114DB address, pass in the this pointer, save the virtual function address to EAX and then call.
Ibaseb *pbaseb = (ibaseb*) p;008114ce mov eax,dword ptr [p] 008114d1 mov dword ptr [PBASEB], EAX pbaseb, FnB () 008114d4 mov eax,dword ptr [pbaseb] 008114d7 mov edx, DWORD ptr [EAX] 008114d9 mov esi,esp 008114DB mov ecx,dword ptr [Pbaseb] 008114DE mov eax,dword ptr [edx] 008114E0 call eax 008114E2 cmp ESI,ESP 008114E4 Call @ILT+
From here we can clearly see how the results are going.
If the correct conversion method is used, what does the execution process look like? As a matter of fact, we all know and know that the IBASEB pointer is actually shifted to the correct position. Combined with anti-Assembly look;
Pbaseb = (ibaseb*) ptest;008114e9 cmp dword ptr [PTest],0 008114ED JE WMAin+0ADh (8114FDh) 008114EF mov eax,dword ptr [pTest] 008114f2 add eax, 8 008114f5 mov dword ptr [EBP-100h],eax 008114FB jmp wmain+0b7h (811507h) 008114FD mov dword ptr [EBP-100h],0 00811507 mov ecx,dword ptr [ebp-100h] 0081150D mov
OK, now the process is clear, in the end, there is a EAX plus 8 operation, the address is directly offset to the correct position.
The above problem word, is the multiple inheritance, must not first convert the this pointer to other types, and then converted to the parent class pointer. As if there is an object delete, make sure that the pointer is the original type and do the delete, otherwise the destructor may not be called and the memory leaks.
C + + Multiple inheritance of the next subclass and the parent-class pointer conversion bug