很早就知道虛函數採用一種叫虛函數表的機制,在類的內存空間中添加一個"隱藏"的成員變量的方法保存了虛函數表的指針。自己便對單繼承的虛函數的執行原理有了些瞭解,但一直不知多繼承怎麼實現,今天就研究了一下。
class Base;
class BaseA;
class BaseB:public virtual Base
class BaseC:public virtual Base
class Derived:public BaseA,public BaseB,public BaseC
反彙編發現Derived的內存分配如下:
*原來沒有vTable的父類就算在繼承列表裏先聲明也不一定放在最前,可能主要考慮這樣vTable統一放最前操作比較方便.
當以Base*調用時:
訪問vFunBase,直接通過前四個字節的vTable(Base)
訪問m_base直接根據偏移量訪問
當以BaseA*調用時:
直接訪問
當以BaseB*調用時:
訪問Base*的vFunBase和m_base通過(this+4)來得到描述Base類位置偏移的說明,從說明中的到與偏移量有關的值(0x04處)
訪問vFunB1,直接通過前四個字節的vTable(Base)
當以BaseC*調用時:
與BaseB相似(此時指針只指向BaseC的變量,在Derived的指針繼承上偏移0x0c)
當以Derived*調用時:
訪問vFunBase與BaseB相同(因爲指向相同)
訪問vFunB1與BaseB相同(因爲指向相同)
訪問vFunC1和vFunC2時,this+0x0c得到BaseC的vTable,然後存取裏面的函數。
原來沒想到會用多個vTable,今天瞭解了,呵呵
其實細想一個vtable和幾個沒有什麼關係,編譯器就知道每一個類型的內存佈局,而調用函數的機器指令是編譯時確定的,這時編譯器已經知道指針類型(不一定是對象真實類型),那就可以知道需要的函數在這個類型的哪個vtable的第幾個位置,生成固定代碼即可。
只要你類型轉換時指針指向正確(參見第二幅圖,轉化爲不同父類型後指針是不一樣的),那麼編譯器就會根據指針指向的類型的內存佈局找到你需要的函數.
*好象調用父類的非虛函數時,傳給函數的this指針也如上圖所示(想想也應該這樣,因爲父類的函數只會按父類的內存佈局操作,有空會驗證一下)
自己發現了vTable-4處有貓膩,但就是搞不明白是什麼,通過http://bbs.pediy.com/showthread.php?t=61873瞭解到一些東西(見最後所附)
由於多態和RTTI的目的,對象內部隱藏的那些指針指向的值應該都是和當前對象的真實類型相關,因此相同類的不同對象中隱藏指針的值應該相同.(不要被我圖中括號括住的"BaseB"等迷惑,那只是爲了說明他們和哪個父類相關)
事實三處RTTITypeDescriptor指針是一樣的,在dynamic_cast < type-id > ( expression ) 裏會直接把RTTITypeDescriptor的地址作爲參數傳入.但三處RTTICompleteObjectLocator指針卻不一樣,現在也沒查到RTTICompleteObjectLocator的前12個字節是什麼.
附:
RTTI(RuniTime TypeInfo)是c++的一種運行時類型識別機制
當一個類的引用或者指針的值去進行“類型識別", 叫做動態識別
當一個類的實例去進行"類型識別", 叫做靜態識別
動態識別需要編譯打開了/GR開關纔可有效, 否則會引起一個運行時錯誤, 而靜態的不需要
對於大型工程而言, 靜態識別並沒有多少意義
實現原理:
這裏假設你對類的虛表非常熟悉, 如果一個類存在虛函數, 或者他的基類存在虛函數,
或者virtual繼承了其他類,或者開啓了RTTI, 那麼編譯器會爲該類生成一個虛表,
該表是一個該類需要虛實現的函數的指針表,一般類的頭4個字節爲該表的指針
(當基類是多重繼承的時候, 非第一個繼承的基類的虛表不是在頭4字節)
當有RTTI在的時候, 虛表-4的位置是一個叫做RTTI Complete Object Locator的描述體
該描述體+c的位置是一個叫做RTTI Type Descriptor的描述體, 該描述體+8的位置是一段
原始類名的buf, 我們只要得到了該buf, 就能得到類的名字.
RTTI Type Descriptor這個描述體在c++中叫做type_info, 他的定義如下
class type_info {
public:
_CRTIMP virtual ~type_info();
_CRTIMP int operator==(const type_info& rhs) const;
_CRTIMP int operator!=(const type_info& rhs) const;
_CRTIMP int before(const type_info& rhs) const;
_CRTIMP const char* name() const;
_CRTIMP const char* raw_name() const;
private:
void *_m_data;
char _m_d_name[1];
type_info(const type_info& rhs);
type_info& operator=(const type_info& rhs);
};
typeid作爲一個c++關鍵字, 給它傳遞一個任意類型的實例, 他能夠返回一個type_info的引用。