这两天试了一下,来说两句
因手头上只有vc6编译器,故只看了vc6的方式
我的测试程序如下 #include "stdafx.h" class mostbase1 { public: mostbase1():i(1){}; int i; }; class mostbase2 { public: mostbase2():j(2){}; int j; };
class base1:public virtual mostbase1,public virtual mostbase2 { }; class base2:public virtual mostbase1,public virtual mostbase2 { }; class derived:public base1,public base2 { }; void f(derived* pderived) { mostbase1* pbase1 = pderived; mostbase2* pbase2 = pderived; int k = pderived->i; k = pderived->j; k = pbase1->i; k = pbase2->j; } int main(int argc, char* argv[]) { derived d; f(&d); printf("Hello World!\n"); return 0; } 经过我的测试及查看汇编代码,得知vc的虚基类转换确如Inside the c++ objects model中所说是用一个virtual base class table来实现的 base1和base2中各有一个__vbct__ptr(大小是4个字节)用来指向一个virtual base class table,该表格存储着base类中虚基类在base类中的偏移,其后就是数据,当derived继承自base1和base2,首先是存放base1和base2的__vbct_ptr,再就是数据,将base1和base2相同基类的数据放到一起 base1类的内存布局如下(base2与base1相同): pvoid* __vbct_base1 ----先是虚基类表的地址,此时__vbct_base1数据如下 ----00 00 00 00 04 00 00 00 08 00 00 00 ----mostbase1在base1中的偏移为[__vbct_base1+4]的值 int i; ----mostbase1类中的i int j; ----mostbase2类中的j
derived类的内存布局如下: pvoid* __vbct_base1; ----base1类的虚基类表的地址,其中的值有变化,数据如下 ----00 00 00 00 08 00 00 00 0C 00 00 00 pvoid* __vbct_base2; ----base2类的虚基类表的地址,数据如下 ----00 00 00 00 04 00 00 00 08 00 00 00 int i; int j;
下面是f(derived* pderived)函数的汇编代码,对其它进行分析
29: void f(derived* pderived) 30: { 0040C230 push ebp 0040C231 mov ebp,esp 0040C233 sub esp,60h 0040C236 push ebx 0040C237 push esi 0040C238 push edi 0040C239 lea edi,[ebp-60h] 0040C23C mov ecx,18h 0040C241 mov eax,0CCCCCCCCh 0040C246 rep stos dword ptr [edi] 31: mostbase1* pbase1 = pderived; 0040C248 cmp dword ptr [ebp+8],0 [1] 0040C24C jne f+27h (0040c257) 0040C24E mov dword ptr [ebp-18h],0 [2] 0040C255 jmp f+35h (0040c265) 0040C257 mov eax,dword ptr [ebp+8] 0040C25A mov ecx,dword ptr [eax] [3] 0040C25C mov edx,dword ptr [ebp+8] 0040C25F add edx,dword ptr [ecx+4] [4] 0040C262 mov dword ptr [ebp-18h],edx 0040C265 mov eax,dword ptr [ebp-18h] 0040C268 mov dword ptr [ebp-4],eax [5] 32: mostbase2* pbase2 = pderived; 0040C26B cmp dword ptr [ebp+8],0 0040C26F jne f+4Ah (0040c27a) 0040C271 mov dword ptr [ebp-1Ch],0 [6] 0040C278 jmp f+58h (0040c288) 0040C27A mov ecx,dword ptr [ebp+8] 0040C27D mov edx,dword ptr [ecx] [7] 0040C27F mov eax,dword ptr [ebp+8] 0040C282 add eax,dword ptr [edx+8] [8] 0040C285 mov dword ptr [ebp-1Ch],eax 0040C288 mov ecx,dword ptr [ebp-1Ch] 0040C28B mov dword ptr [ebp-8],ecx 33: base1* p1 = pderived; 0040C28E mov edx,dword ptr [ebp+8] 0040C291 mov dword ptr [ebp-0Ch],edx [9] 34: p1->i = 1; 0040C294 mov eax,dword ptr [ebp-0Ch] 0040C297 mov ecx,dword ptr [eax] 0040C299 mov edx,dword ptr [ecx+4] [10] 0040C29C mov eax,dword ptr [ebp-0Ch] 0040C29F mov dword ptr [eax+edx],1 35: base2* p2 = pderived; 0040C2A6 cmp dword ptr [ebp+8],0 0040C2AA je f+87h (0040c2b7) 0040C2AC mov ecx,dword ptr [ebp+8] 0040C2AF add ecx,4 [11] 0040C2B2 mov dword ptr [ebp-20h],ecx 0040C2B5 jmp f+8Eh (0040c2be) 0040C2B7 mov dword ptr [ebp-20h],0 0040C2BE mov edx,dword ptr [ebp-20h] 0040C2C1 mov dword ptr [ebp-10h],edx [12] 36: p2->j = 1; 0040C2C4 mov eax,dword ptr [ebp-10h] 0040C2C7 mov ecx,dword ptr [eax] 0040C2C9 mov edx,dword ptr [ecx+8] [13] 0040C2CC mov eax,dword ptr [ebp-10h] 0040C2CF mov dword ptr [eax+edx],1 37: int k = pderived->i; 0040C2D6 mov ecx,dword ptr [ebp+8] 0040C2D9 mov edx,dword ptr [ecx] 0040C2DB mov eax,dword ptr [edx+4] [14] 0040C2DE mov ecx,dword ptr [ebp+8] 0040C2E1 mov edx,dword ptr [ecx+eax] [15] 0040C2E4 mov dword ptr [ebp-14h],edx 38: k = pderived->j; 0040C2E7 mov eax,dword ptr [ebp+8] 0040C2EA mov ecx,dword ptr [eax] 0040C2EC mov edx,dword ptr [ecx+8] [16] 0040C2EF mov eax,dword ptr [ebp+8] 0040C2F2 mov ecx,dword ptr [eax+edx] 0040C2F5 mov dword ptr [ebp-14h],ecx 39: 40: } 0040C2F8 pop edi 0040C2F9 pop esi 0040C2FA pop ebx 0040C2FB mov esp,ebp 0040C2FD pop ebp 0040C2FE ret
[1]dword ptr [ebp+8h]就是pderived,先看看是不是为NULL. [2]dword ptr [ebp-18h]是一个中间变量,当pderived为NULL时,将其也赋为NULL [3]取出__vbct_base1,其位置在derived类的开始处 [4]取出mostbast1类在derived中的偏移,此值在_vbct_base1+4的位置,占用4个字节,其值为8, 因为derived前有两个__vbct_prt,都为4字节,故mostbast1在derived的偏移为8. [5]将pbase1赋值,dword ptr [ebp-4]存放pbase1; [6]同(2),只是中间变量的地址不同 [7]同(3),取出__vbct_base1 [8]同(4), 取出mostbast2类在derived中的偏移,此时为12 [9]取出derived类中的base1的地址,也就是derived中__vbct_base1的地址,可能你有疑问,看下面 [10]用p1存取数据时,还是通过__vbct_base1来做的,通过__vbct_base1得到mostbase1在derived中 的偏移,最后得到的地址是pderived+8 [11]现在取出__vbct_base2的地址,看到了add ecx,4么 [12]将p2赋值 [13]通过__vbct_base2来取得mostbase2的地址,再来存取j [14]通过__vbct_base1来取得mostbase1的地址 [15]偏移在eax中,取出i来 [16]通过__vbct_base1来取得mostbase2的地址
以下是我构想的c伪码,可能不太正确,因为汇编代码已经优化过 void f(derived* pderived) { ----mostbase1* pbase1 = pderived; mostbase1 *pbase1,*temp1; if (pderived == 0) { temp1 = 0; } else { temp1 = (mostbase1*)(pderived+(pderived->__vbct_base1[1])); } pbase1 = temp1;
----mostbase2* pbase2 = pderived; mostbase2 *pbase2,*temp2; if (pderived == 0) { temp2 = 0; } else { temp2 = (mostbase2*)(pderived+(pderived->__vbct_base1[2])); } pbase2 = temp2; ----base1* p1 = pderived; base1* p1 = &pderived->__vbct_base1; ----p1->i = 1; (mostbase1*)(p1+p1->__vbct_base1[1])->i = 1; ----base2* p2 = pderived; base2* p2 = &pderived->__vbct_base2; ----p2->j = 1; (mostbase2*)(p2+p2->__vbct_base2[2])->j = 1; ----int k = pderived->i; int k = (mostbase1*)(pderived+pderived->__vbct_base1[1])->i; ----k = pderived->j; k = (mostbase2*)(pderived+pderived->__vbct_base1[2])->j; }

|