回顧一下以前對虛函數表及虛表指針的概念:
1. 虛函數表屬於類,同類對象間共享該虛函數表(貌似虛函數表裏面維護了一個函數地址的指針數組)。
2.不同對象各自維護一個虛表指針指向類的虛表,類對象大小包含成員變量大小(含虛表指針vptr大小) ——(計算大小要考慮字節對齊 32位默認4字節對齊,64位8字節)。
3.虛表指針在對象內存的開始位置。
4.虛函數調用時,動態聯編的多態特性及運行時判斷執行哪個函數,所以虛函數執行效率比普通函數要低。
5.子類繼承父類後,如果子類有父類的重名虛函數,則會覆蓋(重寫)父類的虛函數,虛表內函數指針也會替換成子類的。
class Base{
virtual void print(){}
};
class Base2{
virtual void dprint(){}
};
class CBase: public Base, public Base2{
};
//g++ align.cpp -fdump-class-hierarchy
-------------------------------------linux 64位系統------------------------------------------------
Vtable for Base //虛函數表內存儲了虛函數的地址
Base::_ZTV4Base: 3u entries //虛表是屬於類,而不是屬於某個具體的對象,一個類只需要一個虛表
0 (int (*)(...))0 //一個空的變長參數函數指針??
8 (int (*)(...))(& _ZTI4Base) //一個指向虛函數表的指針 = 把虛函數表地址轉成變長參數的函數指針?
16 Base::print//24 xxx
Class Base
size=8 align=8
base size=8 base align=8
Base (0x7f1f07d2bd90) 0 nearly-empty
vptr=((& Base::_ZTV4Base) + 16u) //可以看出虛函數表首地址還有兩個函數指針,偏移16字節後是第一個虛函數的地址//虛函數表指針vptr指向的是第一個虛函數,而不是虛函數表首地址
//同理,32位系統指針4字節,則應該是虛函數表指針=虛函數表首地址 + 8字節偏移 = 第一個虛函數地址
Vtable for Base2
Base2::_ZTV5Base2: 3u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Base2)
16 Base2::dprintClass Base2
size=8 align=8
base size=8 base align=8
Base2 (0x7f1f07d2bf50) 0 nearly-empty //每個類的vptr放在類內存空間的起始位置
vptr=((& Base2::_ZTV5Base2) + 16u)
Vtable for CBase
CBase::_ZTV5CBase: 6u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5CBase)
16 Base::print
24 (int (*)(...))-0x00000000000000008
32 (int (*)(...))(& _ZTI5CBase)
40 Base2::dprintClass CBase //C++多重繼承中,會順序存儲所有基類的虛函數表指針(即有多個虛表指針)
size=16 align=8base size=16 base align=8
CBase (0x7f1f07d91380) 0
vptr=((& CBase::_ZTV5CBase) + 16u) //子類複用第一個基類的虛表指針,並對虛表進行覆蓋或者添加
Base (0x7f1f07d96070) 0 nearly-empty
primary-for CBase (0x7f1f07d91380) //第一個基類會用於初始化子類(我認爲是初始化子類虛表),然後子類對象的虛表指針指向這個子類虛表。
Base2 (0x7f1f07d960e0) 8 nearly-empty
vptr=((& CBase::_ZTV5CBase) + 40u) //子類對象第二個虛表指針,指向子類虛表中來自第二個基類的第一個虛函數地址
___________________________________________________________
class Base{
virtual void print(){}
};class CBase: public Base{
};
Vtable for Base
Base::_ZTV4Base: 3u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI4Base)
16 Base::printClass Base
size=8 align=8
base size=8 base align=8
Base (0x7f1eeb8e9d90) 0 nearly-empty
vptr=((& Base::_ZTV4Base) + 16u)Vtable for CBase
CBase::_ZTV5CBase: 3u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5CBase)
16 Base::printClass CBase
size=8 align=8
base size=8 base align=8
CBase (0x7f1eeb954070) 0 nearly-empty
vptr=((& CBase::_ZTV5CBase) + 16u)
Base (0x7f1eeb9540e0) 0 nearly-empty
primary-for CBase (0x7f1eeb954070)
Class Base2
size=8 align=8
base size=8 base align=8
Base2 (0x7f16e5f4ea10) 0 nearly-empty
vptr=((& Base2::_ZTV5Base2) + 16u)
Vtable for CBase
CBase::_ZTV5CBase: 5u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5CBase)
16 Base::print
24 Base::test
32 CBase::ok //子類虛函數地址會放在基類後面
某大牛如是說:子類中聲明的虛函數除了覆蓋各個基類對應函數的指針外,還額外添加一份到第一個基類的vptr中(體現了共用的意義)。我表示對共用這個詞持保留意見,第一虛表屬於各個類,父類和子類不是同一個;第二虛表指針屬於對象,各個對象都有。子類複用繼承下來的父類聲明的虛表指針而已,該指針指向的是子類虛表。