1、没有虚继承,没有虚函数的情况:
class VB { public: int m_a; //virtual void print() { // cout << "VB" << endl; //} }; class Base : public VB { public: int m_b1; //virtual void pb() //{ // cout << "pb" << endl; //} }; class Base2 : public VB { public: int m_b2; //virtual void pb2() //{ // cout << "pb2" << endl; //} }; class Derive : public Base, public Base2 { public: int m_d; //virtual void pb() //{ // cout << "Derive pb" << endl; //} //virtual void pb2() //{ // cout << "Derive pb2" << endl; //} };
vs里得到的内存对象:
1 1>class VB size(4): 2 1> +--- 3 1> 0 | m_a 4 1> +--- 5 1> 6 1>class Base size(8): 7 1> +--- 8 1> 0 | +--- (base class VB) 9 1> 0 | | m_a 10 1> | +--- 11 1> 4 | m_b1 12 1> +--- 13 1> 14 1>class Base2 size(8): 15 1> +--- 16 1> 0 | +--- (base class VB) 17 1> 0 | | m_a 18 1> | +--- 19 1> 4 | m_b2 20 1> +--- 21 1> 22 1>class Derive size(20): 23 1> +--- 24 1> 0 | +--- (base class Base) 25 1> 0 | | +--- (base class VB) 26 1> 0 | | | m_a 27 1> | | +--- 28 1> 4 | | m_b1 29 1> | +--- 30 1> 8 | +--- (base class Base2) 31 1> 8 | | +--- (base class VB) 32 1> 8 | | | m_a 33 1> | | +--- 34 1>12 | | m_b2 35 1> | +--- 36 1>16 | m_d 37 1> +--- 38 1>
从上面信息可以得知:
1)、继承在对象内的布局是按继承的从左到右顺序,依次放到派生类对象内的;
2)、可以看到VB在Derive里面有两份(虚继承就是为了解决这个问题);
3)派生类对象的指针转换为基类指针,只需要进行偏移即可。
2、有虚函数的情况:
class VB { public: int m_a; virtual void print() { cout << "VB" << endl; } }; class Base : public VB { public: int m_b1; virtual void pb() { cout << "pb" << endl; } }; class Base2 : public VB { public: int m_b2; virtual void pb2() { cout << "pb2" << endl; } }; class Derive : public Base, public Base2 { public: int m_d; virtual void pb() { cout << "Derive pb" << endl; } virtual void pb2() { cout << "Derive pb2" << endl; } };
vs里得到的内存对象:
1 1>class VB size(8): 2 1> +--- 3 1> 0 | {vfptr} 4 1> 4 | m_a 5 1> +--- 6 1> 7 1>VB::$vftable@: 8 1> | &VB_meta 9 1> | 0 10 1> 0 | &VB::print 11 1> 12 1>VB::print this adjustor: 0 13 1> 14 1>class Base size(12): 15 1> +--- 16 1> 0 | +--- (base class VB) 17 1> 0 | | {vfptr} 18 1> 4 | | m_a 19 1> | +--- 20 1> 8 | m_b1 21 1> +--- 22 1> 23 1>Base::$vftable@: 24 1> | &Base_meta 25 1> | 0 26 1> 0 | &VB::print 27 1> 1 | &Base::pb 28 1> 29 1>Base::pb this adjustor: 0 30 1> 31 1>class Base2 size(12): 32 1> +--- 33 1> 0 | +--- (base class VB) 34 1> 0 | | {vfptr} 35 1> 4 | | m_a 36 1> | +--- 37 1> 8 | m_b2 38 1> +--- 39 1> 40 1>Base2::$vftable@: 41 1> | &Base2_meta 42 1> | 0 43 1> 0 | &VB::print 44 1> 1 | &Base2::pb2 45 1> 46 1>Base2::pb2 this adjustor: 0 47 1> 48 1>class Derive size(28): 49 1> +--- 50 1> 0 | +--- (base class Base) 51 1> 0 | | +--- (base class VB) 52 1> 0 | | | {vfptr} 53 1> 4 | | | m_a 54 1> | | +--- 55 1> 8 | | m_b1 56 1> | +--- 57 1>12 | +--- (base class Base2) 58 1>12 | | +--- (base class VB) 59 1>12 | | | {vfptr} 60 1>16 | | | m_a 61 1> | | +--- 62 1>20 | | m_b2 63 1> | +--- 64 1>24 | m_d 65 1> +--- 66 1> 67 1>Derive::$vftable@Base@: 68 1> | &Derive_meta 69 1> | 0 70 1> 0 | &VB::print 71 1> 1 | &Derive::pb 72 1> 73 1>Derive::$vftable@Base2@: 74 1> | -12 75 1> 0 | &VB::print 76 1> 1 | &Derive::pb2 77 1> 78 1>Derive::pb this adjustor: 0 79 1>Derive::pb2 this adjustor: 12 80 1>
从上面信息可以得知:
1)VB的大小为8,其中一个虚函数指针,一个int成员变量;
2)Derive的大小为28,其中一个Base(12Byte),一个Base(12Byte),一个自己的成员变量int m_d,一共28Byte;
3)我们知道,对象的虚函数指针指向虚表,虚表里存着函数指针,那么具体是怎么实现的多态呢?
从Base来看:继承了VB,虚函数指针在对象的第0个地址;
从Derive来看:由于继承顺序,所以Base类放在的第一个位置,对应的虚函数指针也按Base里的顺序放置的;
再根据表Derive::$vftable@Base@ 和 Derive::$vftable@Base@,可以得知:在Derive里的,虚函数表的第二项已经被替换为&Derive::pb了,也就是被覆盖了;在调用pb函数时,根据虚函数表指针 + 表里的偏移,得到的就是派生类里的pb函数了;这里需要注意的是:虚函数表指针是怎么被赋值的,从反汇编我们可以知道,在构造函数里,会将虚函数表指针赋值。
Derive::Derive()构造函数的反汇编代码:
1 00C12450 push ebp 2 00C12451 mov ebp,esp 3 00C12453 sub esp,0CCh 4 00C12459 push ebx 5 00C1245A push esi 6 00C1245B push edi 7 00C1245C push ecx 8 00C1245D lea edi,[ebp-0CCh] 9 00C12463 mov ecx,33h 10 00C12468 mov eax,0CCCCCCCCh 11 00C1246D rep stos dword ptr es:[edi] 12 00C1246F pop ecx 13 00C12470 mov dword ptr [this],ecx 14 00C12473 mov ecx,dword ptr [this] 15 00C12476 call Base::Base (0C1150Ah) 16 00C1247B mov ecx,dword ptr [this] 17 00C1247E add ecx,0Ch 18 00C12481 call Base2::Base2 (0C114FBh) 19 00C12486 mov eax,dword ptr [this] 20 00C12489 mov dword ptr [eax],offset Derive::`vftable' (0C1AC28h) //这个是对象的第0个位置,放置了虚函数表的地址 21 00C1248F mov eax,dword ptr [this] 22 00C12492 mov dword ptr [eax+0Ch],offset Derive::`vftable' (0C1AD54h) //这个偏移刚好是第二个虚函数表指针的地址 23 00C12499 mov eax,dword ptr [this] 24 00C1249C pop edi 25 00C1249D pop esi 26 00C1249E pop ebx 27 00C1249F add esp,0CCh 28 00C124A5 cmp ebp,esp 29 00C124A7 call __RTC_CheckEsp (0C11316h) 30 00C124AC mov esp,ebp 31 00C124AE pop ebp 32 00C124AF ret
3、有虚继承的时候:
1 class VB 2 { 3 public: 4 int m_a; 5 virtual void print() { 6 cout << "VB" << endl; 7 } 8 }; 9 class Base : virtual public VB 10 { 11 public: 12 int m_b1; 13 virtual void pb() 14 { 15 cout << "pb" << endl; 16 } 17 }; 18 19 class Base2 : virtual public VB 20 { 21 public: 22 int m_b2; 23 virtual void pb2() 24 { 25 cout << "pb2" << endl; 26 } 27 }; 28 29 class Derive : public Base, public Base2 30 { 31 public: 32 int m_d; 33 virtual void pb() 34 { 35 cout << "Derive pb" << endl; 36 } 37 virtual void pb2() 38 { 39 cout << "Derive pb2" << endl; 40 } 41 };
vs里得到的对象信息:
1 1>class VB size(8): //没有变化 2 1> +--- 3 1> 0 | {vfptr} 4 1> 4 | m_a 5 1> +--- 6 1> 7 1>VB::$vftable@: 8 1> | &VB_meta 9 1> | 0 10 1> 0 | &VB::print 11 1> 12 1>VB::print this adjustor: 0 13 1> 14 1>class Base size(20): //多了一个虚基类表指针 15 1> +--- 16 1> 0 | {vfptr} //虚函数表指针,size 4 17 1> 4 | {vbptr} //虚基类表指针,size 4 18 1> 8 | m_b1 //size 4 19 1> +--- 20 1> +--- (virtual base VB) //大小没变化,就是布局上,被放到Base成员变量的后面了,如果有Base: virtual public vb0, virtual public vb1呢,这个虚继承的顺序将是在Base末端依次布局的顺序(见后面的补充) 21 1>12 | {vfptr} 22 1>16 | m_a 23 1> +--- 24 1> 25 1>Base::$vftable@Base@: 26 1> | &Base_meta 27 1> | 0 28 1> 0 | &Base::pb 29 1> 30 1>Base::$vbtable@: 31 1> 0 | -4 32 1> 1 | 8 (Based(Base+4)VB) 33 1> 34 1>Base::$vftable@VB@: 35 1> | -12 36 1> 0 | &VB::print 37 1> 38 1>Base::pb this adjustor: 0 39 1>vbi: class offset o.vbptr o.vbte fVtorDisp 40 1> VB 12 4 4 0 41 1> 42 1>class Base2 size(20): 43 1> +--- 44 1> 0 | {vfptr} 45 1> 4 | {vbptr} 46 1> 8 | m_b2 47 1> +--- 48 1> +--- (virtual base VB) 49 1>12 | {vfptr} 50 1>16 | m_a 51 1> +--- 52 1> 53 1>Base2::$vftable@Base2@: 54 1> | &Base2_meta 55 1> | 0 56 1> 0 | &Base2::pb2 57 1> 58 1>Base2::$vbtable@: 59 1> 0 | -4 60 1> 1 | 8 (Base2d(Base2+4)VB) 61 1> 62 1>Base2::$vftable@VB@: 63 1> | -12 64 1> 0 | &VB::print 65 1> 66 1>Base2::pb2 this adjustor: 0 67 1>vbi: class offset o.vbptr o.vbte fVtorDisp 68 1> VB 12 4 4 0 69 1> 70 1>class Derive size(36): 71 1> +--- 72 1> 0 | +--- (base class Base)//size 12,末端的虚继承信息被共享了,虚基类表指针也是在构造函数里赋值的 73 1> 0 | | {vfptr} 74 1> 4 | | {vbptr} 75 1> 8 | | m_b1 76 1> | +--- 77 1>12 | +--- (base class Base2) 78 1>12 | | {vfptr} 79 1>16 | | {vbptr} 80 1>20 | | m_b2 81 1> | +--- 82 1>24 | m_d 83 1> +--- 84 1> +--- (virtual base VB) 85 1>28 | {vfptr} 86 1>32 | m_a 87 1> +--- 88 1> 89 1>Derive::$vftable@Base@: 90 1> | &Derive_meta 91 1> | 0 92 1> 0 | &Derive::pb 93 1> 94 1>Derive::$vftable@Base2@: 95 1> | -12 96 1> 0 | &Derive::pb2 97 1> 98 1>Derive::$vbtable@Base@: 99 1> 0 | -4 100 1> 1 | 24 (Derived(Base+4)VB) 101 1> 102 1>Derive::$vbtable@Base2@: 103 1> 0 | -4 104 1> 1 | 12 (Derived(Base2+4)VB) 105 1> 106 1>Derive::$vftable@VB@: 107 1> | -28 108 1> 0 | &VB::print 109 1> 110 1>Derive::pb this adjustor: 0 111 1>Derive::pb2 this adjustor: 12 112 1>vbi: class offset o.vbptr o.vbte fVtorDisp 113 1> VB 28 4 4 0 114 1>
Derive::Derive的反汇编:
1 00BB24E0 push ebp 2 00BB24E1 mov ebp,esp 3 00BB24E3 sub esp,0CCh 4 00BB24E9 push ebx 5 00BB24EA push esi 6 00BB24EB push edi 7 00BB24EC push ecx 8 00BB24ED lea edi,[ebp-0CCh] 9 00BB24F3 mov ecx,33h 10 00BB24F8 mov eax,0CCCCCCCCh 11 00BB24FD rep stos dword ptr es:[edi] 12 00BB24FF pop ecx 13 00BB2500 mov dword ptr [this],ecx 14 00BB2503 cmp dword ptr [ebp+8],0 15 00BB2507 je Derive::Derive+48h (0BB2528h) //这里有个判断 16 00BB2509 mov eax,dword ptr [this] 17 00BB250C mov dword ptr [eax+4],offset Derive::`vbtable' (0BBAC8Ch) //Derive::$vbtable@Base@: 18 00BB2513 mov eax,dword ptr [this] 19 00BB2516 mov dword ptr [eax+10h],offset Derive::`vbtable' (0BBAC98h) //Derive::$vbtable@Base2@: 20 00BB251D mov ecx,dword ptr [this] 21 00BB2520 add ecx,1Ch 22 00BB2523 call VB::VB (0BB1267h) //基类的构造函数 23 00BB2528 push 0 24 00BB252A mov ecx,dword ptr [this] 25 00BB252D call Base::Base (0BB1334h) //基类的构造函数 26 00BB2532 push 0 27 00BB2534 mov ecx,dword ptr [this] 28 00BB2537 add ecx,0Ch 29 00BB253A call Base2::Base2 (0BB1244h) //基类的构造函数 30 00BB253F mov eax,dword ptr [this] 31 00BB2542 mov dword ptr [eax],offset Derive::`vftable' (0BBAC74h) //虚函数表的设置则是基类构造函数call之后 32 00BB2548 mov eax,dword ptr [this] 33 00BB254B mov dword ptr [eax+0Ch],offset Derive::`vftable' (0BBAC50h) 34 00BB2552 mov eax,dword ptr [this] 35 00BB2555 mov ecx,dword ptr [eax+4] 36 00BB2558 mov edx,dword ptr [ecx+4] 37 00BB255B mov eax,dword ptr [this] 38 00BB255E mov dword ptr [eax+edx+4],offset Derive::`vftable' (0BBAC84h) //从布局可以看到,一共有三个虚函数表指针,这是共享的那个VB的虚函数表指针的偏移 39 00BB2566 mov eax,dword ptr [this] 40 00BB2569 pop edi 41 00BB256A pop esi 42 00BB256B pop ebx 43 00BB256C add esp,0CCh 44 00BB2572 cmp ebp,esp 45 00BB2574 call __RTC_CheckEsp (0BB1325h) 46 00BB2579 mov esp,ebp 47 00BB257B pop ebp 48 00BB257C ret 4
从上面汇编分析得知,虚基类表的指针,最终指向;虚函数表指针在构造函数后赋值,最终指向派生类的虚函数表指针。
4、下面考虑如何将派生类指针转为基类指针?
1)没有虚函数 + 没有虚继承情况下,指针偏移即可;
2)有虚函数 + 没有虚继承,指针偏移即可,刚好虚函数指针在每个类型的第0个位置,多态执行函数很方便,可以使用dynamic_cast动态转换;
3)没有虚函数 + 虚继承,是否可以用dynamic_cast运行期转换:不可以;
4)有虚继承的情况,我们看下面的转换:
1 Derive* d = new Derive(); 2 00132D78 push 24h 3 00132D7A call operator new (0131401h) 4 00132D7F add esp,4 5 00132D82 mov dword ptr [ebp-11Ch],eax 6 00132D88 cmp dword ptr [ebp-11Ch],0 7 00132D8F je fun2+78h (0132DC8h) 8 00132D91 xor eax,eax 9 00132D93 mov ecx,dword ptr [ebp-11Ch] 10 00132D99 mov dword ptr [ecx],eax 11 00132D9B mov dword ptr [ecx+4],eax 12 00132D9E mov dword ptr [ecx+8],eax 13 00132DA1 mov dword ptr [ecx+0Ch],eax 14 00132DA4 mov dword ptr [ecx+10h],eax 15 00132DA7 mov dword ptr [ecx+14h],eax 16 00132DAA mov dword ptr [ecx+18h],eax 17 00132DAD mov dword ptr [ecx+1Ch],eax 18 00132DB0 mov dword ptr [ecx+20h],eax 19 00132DB3 push 1 20 00132DB5 mov ecx,dword ptr [ebp-11Ch] 21 00132DBB call Derive::Derive (0131078h) 22 00132DC0 mov dword ptr [ebp-124h],eax 23 00132DC6 jmp fun2+82h (0132DD2h) 24 00132DC8 mov dword ptr [ebp-124h],0 25 00132DD2 mov edx,dword ptr [ebp-124h] 26 00132DD8 mov dword ptr [d],edx 27 Base* b = d; 28 00132DDB mov eax,dword ptr [d] //从内存布局中,我们知道Base是第一个位置的,所以直接把地址给b 29 00132DDE mov dword ptr [b],eax 30 Base2* b2 = d; 31 00132DE1 cmp dword ptr [d],0 32 00132DE5 je fun2+0A5h (0132DF5h) 33 00132DE7 mov eax,dword ptr [d] 34 00132DEA add eax,0Ch //Base2在Derive中的偏移是12 35 00132DED mov dword ptr [ebp-124h],eax 36 00132DF3 jmp fun2+0AFh (0132DFFh) 37 00132DF5 mov dword ptr [ebp-124h],0 38 00132DFF mov ecx,dword ptr [ebp-124h] 39 00132E05 mov dword ptr [b2],ecx 40 VB* vb = d; 41 00132E08 cmp dword ptr [d],0 42 00132E0C jne fun2+0CAh (0132E1Ah) 43 00132E0E mov dword ptr [ebp-124h],0 44 00132E18 jmp fun2+0E0h (0132E30h) 45 00132E1A mov eax,dword ptr [d] 46 00132E1D mov ecx,dword ptr [eax+4] //虚基类表指针,Derive::$vbtable@Base@ 47 00132E20 mov edx,dword ptr [ecx+4] //得到偏移,24 48 00132E23 mov eax,dword ptr [d] 49 00132E26 lea ecx,[eax+edx+4] //this + 偏移24 + 4 =this+28,刚好等于VB在Derive中的对象内存位置 50 00132E2A mov dword ptr [ebp-124h],ecx 51 00132E30 mov edx,dword ptr [ebp-124h] 52 00132E36 mov dword ptr [vb],edx 53 VB*vb1 = b; 54 00132E39 cmp dword ptr [b],0 55 00132E3D jne fun2+0FBh (0132E4Bh) 56 00132E3F mov dword ptr [ebp-124h],0 57 00132E49 jmp fun2+111h (0132E61h) 58 00132E4B mov eax,dword ptr [b] 59 00132E4E mov ecx,dword ptr [eax+4] 60 00132E51 mov edx,dword ptr [ecx+4] 61 00132E54 mov eax,dword ptr [b] 62 00132E57 lea ecx,[eax+edx+4] 63 00132E5B mov dword ptr [ebp-124h],ecx 64 00132E61 mov edx,dword ptr [ebp-124h] 65 00132E67 mov dword ptr [vb1],edx 66 VB*vb2 = b2; 67 00132E6A cmp dword ptr [b2],0 68 00132E6E jne fun2+12Ch (0132E7Ch) 69 00132E70 mov dword ptr [ebp-124h],0 70 VB*vb2 = b2; 71 00132E7A jmp fun2+142h (0132E92h) 72 00132E7C mov eax,dword ptr [b2] 73 00132E7F mov ecx,dword ptr [eax+4] 74 00132E82 mov edx,dword ptr [ecx+4] 75 00132E85 mov eax,dword ptr [b2] 76 00132E88 lea ecx,[eax+edx+4] 77 00132E8C mov dword ptr [ebp-124h],ecx 78 00132E92 mov edx,dword ptr [ebp-124h] 79 00132E98 mov dword ptr [vb2],edx
5、多个虚继承下的内存对象信息:
1 class VB 2 { 3 public: 4 int m_a; 5 virtual void print() { 6 cout << "VB" << endl; 7 } 8 }; 9 class VB2 10 { 11 public: 12 int m_a2; 13 virtual void print2() { 14 cout << "VB" << endl; 15 } 16 }; 17 class Base : virtual public VB,virtual public VB2 18 { 19 public: 20 int m_b1; 21 virtual void pb() 22 { 23 cout << "pb" << endl; 24 } 25 };
1 1>class VB size(8): 2 1> +--- 3 1> 0 | {vfptr} 4 1> 4 | m_a 5 1> +--- 6 1> 7 1>VB::$vftable@: 8 1> | &VB_meta 9 1> | 0 10 1> 0 | &VB::print 11 1> 12 1>VB::print this adjustor: 0 13 1> 14 1>class VB2 size(8): 15 1> +--- 16 1> 0 | {vfptr} 17 1> 4 | m_a2 18 1> +--- 19 1> 20 1>VB2::$vftable@: 21 1> | &VB2_meta 22 1> | 0 23 1> 0 | &VB2::print2 24 1> 25 1>VB2::print2 this adjustor: 0 26 1> 27 1>class Base size(28): 28 1> +--- 29 1> 0 | {vfptr} 30 1> 4 | {vbptr} 31 1> 8 | m_b1 32 1> +--- 33 1> +--- (virtual base VB) 34 1>12 | {vfptr} 35 1>16 | m_a 36 1> +--- 37 1> +--- (virtual base VB2) 38 1>20 | {vfptr} 39 1>24 | m_a2 40 1> +--- 41 1> 42 1>Base::$vftable@Base@: 43 1> | &Base_meta 44 1> | 0 45 1> 0 | &Base::pb 46 1> 47 1>Base::$vbtable@: 48 1> 0 | -4 49 1> 1 | 8 (Based(Base+4)VB) 50 1> 2 | 16 (Based(Base+4)VB2) 51 1> 52 1>Base::$vftable@VB@: 53 1> | -12 54 1> 0 | &VB::print 55 1> 56 1>Base::$vftable@VB2@: 57 1> | -20 58 1> 0 | &VB2::print2 59 1> 60 1>Base::pb this adjustor: 0 61 1>vbi: class offset o.vbptr o.vbte fVtorDisp 62 1> VB 12 4 4 0 63 1> VB2 20 4 8 0 64 1>
可以看到Base里只有一个虚基类表指针,且指向的内容是:
1>Base::$vbtable@:
1> 0 | -4
1> 1 | 8 (Based(Base+4)VB)
1> 2 | 16 (Based(Base+4)VB2)