在VS2013編程,調試
問題 : 菱形繼承會引來,二義性
1.源代碼
#includeusing namespace std; class Base { public: virtual void FunTest() { cout << "Base::FunTest () " << endl; } virtual void FunTest1() { cout << "Base::FunTest1 () " << endl; } }; class C1 :virtual public Base { public: virtual void FunTest2() { cout << "C1::FunTest2 () " << endl; } }; class C2 :virtual public Base { public: virtual void FunTest3() { cout << "C2::FunTest3 () " << endl; } }; class Der : public C1, public C2 { public: virtual void FunTest4() { cout << "Der::FunTest4 () " << endl; } virtual void FunTest5() { cout << "Der::FunTest5 () " << endl; } virtual void FunTest6() { cout << "Der::FunTest6 () " << endl; } }; typedef void(*vftab) (); void Test() { Der d; cout << sizeof(d) << endl; cout << "-------C1---- " << endl; int *vfpt = (int *)(*(int *)&d); vftab vft = (vftab)(*(int *)vfpt); while (vft != 0) { vft(); vfpt++; vft = (vftab)(*vfpt); } cout << endl; cout << "-------C2---- " << endl; vfpt = (int *)(*((int *)&d + 2)); vft = (vftab)(*(int *)vfpt); while (vft != 0) { vft(); vfpt++; vft = (vftab)(*vfpt); } cout << endl; cout << "-------Base---- " << endl; vfpt = (int *)(*((int *)&d + 4)); vft = (vftab)(*(int *)vfpt); while (vft != 0) { vft(); vfpt++; vft = (vftab)(*vfpt); } } int main() { Test(); getchar(); return 0; }
運行結果:
這些結果是怎麼來的哪?為什麼d的大小為20個字節??
接下來就看看d的內存吧
現在知道了為什麼d的大小為20個字節了吧!但是問題有來了,d的內存中存的都是些什麼東西哪?
一步一步看吧!
貌似這些都是地址,那就看看這些地址有存了些什麼
一. 看一下到 地址(0x 00 2a dd 2c)
可以從監視1看到 地址(0x 00 2a 11 59)是虛函數C1::FunTest2()的入口地址
可以從監視1看到 地址(0x 00 2a 12 53)是虛函數Der::FunTest4()的入口地址
可以從監視1看到 地址(0x 00 2a 13 de)是虛函數Der::FunTest5()的入口地址
可以從監視1看到 地址(0x 00 2a 10 05)是虛函數Der::FunTest6()的入口地址
總結1:地址(0x 00 2a dd 2c),應該是類Der從類C1繼承下來虛表的地址,虛表中存的是類Der和類C1的虛函數的地址
二. 看一下到 地址(0x 00 2a dd 5c)
可以看到 0x 00 4e f9 30 + 0x 00 00 00 0c =0x 00 4e f9 3c
總結2:地址(0x 00 2a dd 5c)下,應該是存的是偏移
三. 看一下到 地址(0x 00 2a dd 44)
可以從監視1看到 地址(0x 00 2a 10 69)是虛函數C2::FunTest3()的入口地址
總結3:地址(0x 00 2a dd 44),應該是類Der從類C2繼承下來虛表的地址,虛表中存的是類C2的虛函數的地址
四. 看一下到 地址(0x 00 2a dd 68)
可以看到 0x 00 4e f9 38 + 0x 00 00 00 04 =0x 00 4e f9 3c
總結4:地址(0x 00 2a dd 68)下,應該是存的是偏移
五.看一下到 地址(0x 00 2a dd 50)
可以從監視1看到 地址(0x 00 2a 12 c6) 是虛函數Base::FunTest()的入口地址
可以從監視1看到 地址(0x 00 2a 12 7b) 是虛函數Base::FunTest1()的入口地址
總結5:地址(0x 00 2a dd 50),應該是類Der從類Base繼承下來虛表的地址,虛表中存的是類Base的虛函數的地址
根據上面的一步一步的分析 :可以得到菱形虛擬繼承(含有虛函數,但沒有被重寫)的模型:
看看這兩個的偏移,就是它們保證了類 Der在繼承 類Base的虛擬函數,以及類Base的數據成員的唯一性,從而避免了二義性的產生。
注意:在函數Test()中的地址偏移,是為了方便從內存中取得地址,查看裡面是什麼內容,如果每個類加上自己的數據成員,那就不是那樣取值了,
我說的是什麼哪 ?意思就是說 :
vfpt = (int *)(*((int *)&d + 2));
vfpt = (int *)(*((int *)&d + 4));
看見 2 和 4 了吧,說的就是這個