1. 程式人生 > >ATL中宏定義offsetofclass的分析

ATL中宏定義offsetofclass的分析

推斷 文章 指向 [] atl 嘗試 tla water appdata

近日學習ATL,通過對宏定義offsetofclass的解惑過程。順便分析下虛函數表,以及通過虛函數表調用函數的問題。


1 解開ATL中宏定義offsetofclass的疑惑 #define _ATL_PACKING 8 #define offsetofclass(base, derived) ((unsigned long)(static_cast <base*>((derived*)_ATL_PACKING))-_ATL_PACKING) 分析例如以下:(base 基類 , derived 子類)

  • (derived*) 8 就是把指針指向地址8。這樣就不用自己新創建類的對象。

  • 此後又static_cast <base*>,將指針轉為基類指針。這個過程,指針的值實際發生了變化。假設有偏移,那麽此時已經指到新的地址,比方12或16(32位系統指針為4字節)
  • 12減去8 就是最後得到的偏移量4
  • 能夠看出。_ATL_PACKING 實際上能夠是隨意非0值,它僅僅是一個地址值,僅僅要不是0,正負均可。
由此得出 offsetofclass 用來計算基類(base)指針在子類(derived)對象中的偏移量。也能夠理解為基類虛函數表在子類對象中的偏移量。

由於虛函數表指針就在全部對象的開頭位置。此時大家多有疑問,為什麽不通過類對象來計算?有一個問題,假設

子類是個虛類。它根本就不能創建類對象,所以就沒法計算。這種方法攻克了虛類的問題。它僅僅是用了下這個地址,並沒有改動數據。(這樣隨意指向內存地址,不知道有何風險?)
如有兩個基類。就有兩個虛函數表指針。 class Derived: public Base1 ,public Base2 offsetofclass(Base1,Derived) 計算出 Base1 Derived的實例對象中偏移0 字節 offsetofclass(Base2,Derived) 計算出 Base2Derived的實例對象中偏移4 字節
2 通過偏移來指定基類在子類對象中的地址
Derived d; Derived *pD= &d; // pD地址0x0018fe98 Base2 * pB2 = pD; //傳遞給pB2。地址為0x0018fe9c,偏移了4個字節 pB2 = (Base2*)(( int)(&d) + 4); // 通過偏移也能夠得到Base2
pB2 和pD地址並不同樣。而指針推斷卻相等。 if(pD == pB) { // 兩個指針的比較 // pD地址0x0018fe98,pB地址0x0018fe9c // 為什麽還是相等呢,pD,pB指向的類型不同,pD先轉換成基類pB類型。再進行比較。 }

例如以下圖: 技術分享技術分享 3 通過虛函數表來調用父類或子類中成員函數 虛函數表。幾個基類分支,就有幾個虛函數表指針 class Derived: public Base1,public Base2 所以Derived有兩個虛函數表指針 例如以下圖: 技術分享

技術分享 Derived覆蓋了基類的同樣虛函數。自己的虛函數放在第一個表中。 清楚了虛函數表。就能夠通過地址來調用函數了.
typedef void (*Fun)( void); //函數指針 Derived d; int **pVtab = (int **)&d; Fun pFun = (Fun)pVtab[0][0]; //等同於 調用 Fun pFun = (Fun)*((int*)*(int*)((int*)&b + 0) + 0); pFun(); 以此類推,調用pVtab[0][1],pVtab[0][2],pVtab[1][0]。pVtab[1][1]
輸出例如以下圖: 技術分享 技術分享

4 例如以下為所有代碼部分 // 測試分3次進行。進行測試1時,請凝視掉其它部分,以此類推。
#include <iostream> using namespace std;
#define _ATL_PACKING 8 // 嘗試改動下 非0 就可以 #define offsetofclass(base, derived) ((unsigned long)(static_cast <base*>((derived*)_ATL_PACKING))-_ATL_PACKING)
typedef void (*Fun)( void);
class Base1 { public: virtual void f() { cout << "Base1::f" << endl; } virtual void g() { cout << "Base1::g" << endl; } }; class Base2 { public : virtual void f() { cout << "Base2::f" << endl; } virtual void g() { cout << "Base2::g" << endl; } // void h(){ cout << "Base2::h" << endl; } // 測試2使用: 非虛函數,此函數不在虛表中 }; class Derived: public Base1 ,public Base2 { public : virtual void f() { cout << "Derived::f" << endl; } virtual void g1() { cout << "Derived::g1" << endl; } // virtual void h() = 0; //測試1使用:子類為虛類時,計算偏移 };
int main(int argc, char* argv[]) { //測試1:子類為虛類時,計算偏移 unsigned long nOffset1 =0,nOffset2=0 ; nOffset1 = offsetofclass(Base1,Derived); // 計算後 nOffset1 =0 nOffset2 = offsetofclass(Base2,Derived); // 計算後 nOffset2 = 4 //測試2:創建對象 unsigned long nOffset1 =0,nOffset2=0 ; nOffset1 = offsetofclass(Base1,Derived); // 計算後 nOffset1 =0 nOffset2 = offsetofclass(Base2,Derived); // 計算後 nOffset2 = 4
Derived d; Derived *pD= &d; //pD地址0x0018fe98 Base2 * pB2 = pD; // 傳遞給pB2,地址為0x0018fe9c,偏移了4個字節 pB2 = (Base2*)(( int)(&d) +nOffset2); // 通過偏移也能夠得到Base2

// 測試3 通過虛函數表調用 函數 Derived d; Derived *pD= &d; int **pVtab = (int **)&d; Fun pFun = (Fun)pVtab[0][0]; //等同於 調用 Fun pFun = (Fun)*((int*)*(int*)((int*)&d+ 0) + 0); pFun(); pFun = (Fun)pVtab[0][1]; //等同於 調用 pFun = (Fun)*((int*)*(int*)((int*)&d + 0 ) + 1); pFun(); pFun = (Fun)pVtab[0][2]; //等同於 調用 pFun = (Fun)*((int*)*(int*)((int*)&d + 0) + 2); pFun(); pFun = (Fun)pVtab[1][0]; //等同於 調用 pFun = (Fun)*((int*)*(int*)((int*)&d + 1) + 0); pFun(); pFun = (Fun)pVtab[1][1]; //等同於 調用 pFun = (Fun)*((int*)*(int*)((int*)&d + 1) + 1); pFun();


int nWait=0; cin >> nWait; }
寫本文之前閱讀參考了下面文章: 1: http://blog.csdn.net/haoel/article/details/1948051/
對於這篇文章中提到的 虛函數表在(Windows XP+VS2003)的末尾是個 NULL值,但筆者用(vs2003和vs2013 +win7 debug,release)測試後 末尾並不是一定是NULL。值不確定。

2: http://blog.csdn.net/wishfly/article/details/2046361 3: http://c.chinaitlab.com/basic/748017.html




ATL中宏定義offsetofclass的分析