青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

Welcome to ErranLi's Blog!

  C++博客 :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
  106 Posts :: 1 Stories :: 97 Comments :: 0 Trackbacks

常用鏈接

留言簿(12)

搜索

  •  

積分與排名

  • 積分 - 177454
  • 排名 - 152

最新評論

閱讀排行榜


引用:http://www.shnenglu.com/stdyh/archive/2007/01/08/17442.html

C++對象內(nèi)存布局


寫這個文章完全是因為想要搞清楚 vc 怎么布局每個 c++ 對象,以及怎樣完成指針的轉換的過程.
  先問一個問題,兩個不同類型的指針相互轉換以后,他們在數(shù)值上是一樣的嗎?比如:

    int nValue = 10;
    int *pInt = &nValue;
    void *pVoid = pInt;
    char *pChar = (char*)pInt;


   這些指針的值(不是說指針指向的內(nèi)存的內(nèi)容)是一樣的嗎? 如果你的回答是 yes,那如果是一個類的繼承體系呢?在繼承類向基類轉換的過程中,指針的數(shù)值還是不變化的么?如果你的回答是"不一定會變化,要看類的體系是怎么設計的 "的話,那恭喜你,不用看下去了.如果你還不確定究竟變還是不變,究竟哪些變,哪些不變,究竟為什么要變?yōu)槭裁床蛔兊脑?接著看下來.

   c++ 標準不規(guī)定 c++ 實現(xiàn)的時候的對象的具體的內(nèi)存布局,除了在某些方面有小的限制以外,c++ 對象在內(nèi)存里面的布局完全是由編譯器自行決定,這里我也只是討論 vc++ .net 2003 build 7.1.3091 的實現(xiàn)方式,我并沒有在 vc5 vc6 vc.net 2002 以及其他的 2003 build 上面做過測試,結論也許不適合那些編譯平臺.這些屬于編譯器具體實現(xiàn),ms 保留有在不通知你我的情況下作出更改的權利.廢話這么多,馬上開始.

  對于 c 的內(nèi)建指針的轉換,結果是不用多討論的,我們只是討論 c++ 的對象.從最簡單的開始.

    class CBase
    {
    public:
      int m_nBaseValue;
    };


  這樣的一個類在內(nèi)存里放置是非常簡單的,他占有4個 bytes 的空間,不用多說,我們從他派生一個類出來.

    class CDerive1 : public CBase
    {
    public:
      int m_nDerive1Value;
    };


   CDerive1 的對象在內(nèi)存里面是怎么放的呢? 也很簡單,占有8個 bytes 的空間,前4個 bytes 屬于 CBase 類,后四個 bytes 屬于自己.一個CDerive1 的指針轉換成一個 CBase 的指針,結果是一樣的.下面我們加上多重繼承看看.

    class CFinal : public CDerive,public CBase // 這里的 CDerive 是一個和 CBase 差不多的基類
    {
    public:
      int m_nFinalValue;
    };


   CFinal 的對象在內(nèi)存里面的布局稍微復雜一點,但是也很容易想象,他占有 12 個 bytes 的空間,前4個屬于 CDerive,中間4個屬于 CBase,后面4個才是自己的.那一個 CFinal 的指針轉換成一個 CDerive 指針,數(shù)值會變么? 轉換成一個 CBase 指針呢?又會變化么?答案是,前一個不變,后一個要變化,道理非常的明顯,CFinal 對象的開頭剛好是一個 CDerive 對象,而 CBase 對象卻在 CFinal 對象的中間,自然是要變化的了,具體怎么變化呢? 加 4 就 ok(自然要檢查是否是空指針).

    CBase *pBase = pFinal ? (CBase*)((char*)pFinal + sizeof(CDerive)) : 0;// 當你寫下 pBase = pFinal 的時候,其實是這樣的

  這種不帶 virtual 的繼承就這么簡單,只是加上一個 offset 而已.下面我們看看如果加上 virtual function 的時候是什么樣子的呢?
還是從簡單類開始.

    class CBase
    {
    public:
      virtual void VirtualBaseFunction(){}
      int m_nBaseValue;
    };


   這里刻意沒有使用 virtual destructor,因為這個函數(shù)稍微有些不同.還是同樣的問題,CBase 類在內(nèi)存上占多大的空間?還是 4 bytes 么? 答案是 no, 在我的編譯器上面是 8 bytes,多出來的 4 bytes 是 __vfptr(watch 窗口看見的名字),他是一個指針,指向了類的 vtable,那什么是 vtable 呢,他是用來干什么的呢? vtable 是用來支援 virtual function 機制的,他其實是一個函數(shù)指針數(shù)組(并不等同于c/c++語言里面的指針數(shù)組,因為他們的類型并不一定是一樣的.)他的每一個元素都指向了一個你定義的 virtual function,這樣通過一個中間層來到達動態(tài)連編的效果,這些指針是在程序運行的時候準備妥當?shù)?而不是在編譯的時候準備妥當?shù)?這個就是動態(tài)聯(lián)編的 目的,具體是由誰來設置這些指針的呢?constructor/destructor/copy constructor/assignment operator他們完成的,不用奇怪,編譯器會在你寫的這些函數(shù)里面安插些必要的代碼用來設置 vtable 的值,如果你沒有寫這些函數(shù),編譯器會在適當?shù)臅r候幫你生成這些函數(shù).明白一點, vtable 是用來支持 virtual function 機制的,而需要 virtual 機制的類基本上都會由一個 __vfptr 指向他自己的 vtable.在調(diào)用 virtual function的時候,編譯器這樣完成:

   pBase->VirtualBaseFunction(); => pBase->__vfptr[0]();// 0 是你的virtual function 在 vtable 中的 slot number,編譯器決定

   現(xiàn)在應該很想象 CBase 的大小了吧,那這個 __vfptr 是放到什么位置的呢? 在 m_nBaseValue 之前還是之后呢? 在我的編譯器上看來,是在之前,為什么要放到之前,是因為在通過 指向類成員函數(shù)的指針調(diào)用 virtual function 的時候能少些代碼(指匯編代碼),這個原因這里就不深入討論了,有興趣的同學可以看看 inside the c++ object model 一書.
  接下來,我們加上繼承來看看.

    class CDerive1 : public CBase
    {
    public:
      virtual void VirtualDerive1Function();
    };


   這個時候你也許要說,內(nèi)存布局跟沒有 virtual 是一樣的,只不過每個類多了一個 __vfptr 而已,呃...這個是不對的,在我的編譯器上面 兩個類共享同一個 __vfptr, vtable 里面放有兩個指針,一個是兩個類共享的,一個只屬于 CDerive1 類,調(diào)用的時候如何呢?

   pDerive1->VirtualDerive1Function() => pDerive1->__vfptr[1]();
   pDerive1->VirtualBaseFunction() => pDerive1->__vfptr[0]();


  至于指針的相互轉換,數(shù)值還是沒有變化的(也正是追求這種效果,所以把 __vfptr 放到類的開頭,因為調(diào)整 this 指針也是要占有運行時的時間的).

   現(xiàn)在加上多重繼承瞧瞧,代碼我不寫上來了,就跟上面的 CFinal, CDerive, CBase 體系一樣,只是每個類多一個VirtualxxxFunction出來,這個時候的指針調(diào)整還是沒有什么變化,所以我們只是看看 vtable 的情況,你會說 CDerive 和 CFinal 共享一個 __vfptr,而 CBase 有一個自己的 __vfptr,而 CFinal 的 __vfptr 有 2 個slot,這個結論是正確的. 同時你也會說 通過 CFinal 類調(diào)用 CBase 的函數(shù)是要進行指針調(diào)整的,yes you'r right,不僅僅是 this 指針調(diào)整(呃,this 指針會成為 function 的一個參數(shù)),還要調(diào)整 vtable 的值:

   pFinal->VirtualBaseFunction() => (CBase*)((char*)pFinal + sizeof(CDerive))->__vfptr[0]();

   轉換成 asm 的代碼大約是這樣的:

   mov eax,[pFinal] ; pFinal is a local object,pFinal will be epb - xx
   add eax,8 ; 8 = sizeof(CDerive)
   mov ecx,eax ; ecx is this pointer
   mov edx,[eax] ; edx = vtable address
   call [edx] ; call vtable[0]


  寫到這里也就明白this指針是怎么調(diào)整的.帶 virtual function 的繼承也不復雜,this指針調(diào)整也是很簡單的,下面看最復雜的部分 virtual inheritance.

   我的編譯器支持虛擬繼承的方式和虛函數(shù)的方式差不多,都是通過一個 table 完成,只是這個就看不到 vc 賦予的名字了,我們叫他 vbtable 吧,編譯器同樣在類里面加入一個指向 vbtable 的指針,我們叫他 __vbptr 吧,這個指針指向了 vbtable ,而 vbtable 里面的每一項對應了一個基類,vbtable 記錄了每個基類的某一個偏移量,通過這個偏移量就能計算出具體類的指針的位置.看個簡單的例子:

   class CBase
   {
   public:
     virtual ~CBase(){}
   };

   class CMid1 : public virtual CBase
   {
   public:
     virtual ~CMid1(){}
     int m_nMid1;
   };

   class CMid2 : public virtual CBase
   {
   public:
     virtual ~CMid2(){}
     int m_nMid2;
   };

   class CFinal : public CMid1,public CMid2
   {
   public:
     virtual ~CFinal(){}
     int m_nFinal;
   };

   CFinal final;
   CFinal *pFinal = &final;??? // pFinal = 0x0012feb4;
   CBase *pBase = pFinal; // pBase = 0x0012fec8 = pFinal + 0x14;
   CMid1 *pMid1 = pFinal; // pMid1 = 0x0012feb4 = pFinal;
   CMid2 *pMid2 = pFinal; // pMid2 = 0x004210b4 = pFinal;


   結果讓你吃驚嗎? 最奇怪的地方居然是 CMid2 和 CMid1 的地址居然是一樣的,這個是因為 vc 把 vbtable 放到了 CFinal 類的開頭的原因,而CMid1 和 CMid2 也同樣要使用這個 vbtable, 所以 這個三個的地址也就必須相同了.那 CBase 的地址是怎么出來的呢? 呃...剛剛我們說了 vbtable 放到了CFinal 的開頭(vc 一定會放在開頭嗎?答案是不一定,這個稍后解釋).在我的機器上面 final 對應內(nèi)存的第一個 dword 是 0x00426030,查看這個地址,第一個dword 是 0 ,第二個就是 0x14,剛好和 pBase 的偏移相同,這個只是巧合,也許你換個類的繼承體系就完全不同了,但是我只是想說明一點,基類的偏移計算是和 vbtable 的值相關聯(lián)的.下面我們就來看看 vc 是怎么計算這些偏移的.
  vc 在分析我們的代碼的時候,生成了一份類的繼承體系信息,其中有一個叫 thisDisplacement 的_PMD結構:

    struct _PMD // total undocumented
    {
      int mdisp; // i think the meaning is Multiinheritance DISPlacement
      int pdisp; // Pointer to vbtable DISPlacement
      int vdisp; // Vbtable DISPlacement
    };


   結構的名字和成員變量的名字確確實實是 vc 的名字(在 watch 窗口輸入 (_PMD*)0 就能看到這個結構的詳細信息),每個字段的含義卻是我自己猜測出來的.mdisp 大概用來表示多重繼承(包括單一繼承)的時候的偏移量,pdisp 表示 vbtable 的偏移量,而 vdisp 表示類在 vbtable 里面的下標.那么有了這個結構怎樣才能完成指針的轉換呢?假如我們有一個派生類指針 pFinal,要轉換成一個特定的基礎類,我們首先要知道和這個基類對應的 _PMD 結構的信息(這個信息的獲取,我暫時沒有找到一個非常方便的方法,現(xiàn)在我使用的方法下面會有描述),有了這個信息以后,轉換就方便了.首先找到 vbtabel 的地址 *(pFinal + pdisp),然后找到基類的偏移 *(*(pFinal + pdisp) + vdisp) 這個偏移值是相對vbtable的,所以還要加上 vbtable的偏移,最后加上 mdisp的偏移,如下:

  char *pFinal = xxx; // need a init value
  char *pBase; // we must calc
  pBase = pFinal + mdisp + *(int *)(*(int *)(pFinal + pdisp) + vdisp) + pdisp;


  注意: 當 pdisp < 0 的時候就表示這個類沒有 vbtable 直接使用 pFinal + mdisp 就得到結果了.
  所以這個結構是一個通用的結構,專門用作類型轉換,不管是有無虛繼承都能使用這個結構進行類型轉換.

  通過這個結構,我們也能看到 vc 是怎樣布局這個 object 的.

  看到這里,也許你要大呼一口氣,媽媽呀,一個類型轉換要這么的麻煩嗎?我直接寫 pBase = pFinal 不就可以了嗎? 恭喜你還沒有被我忽悠得暈頭轉向,哈哈.其實你寫下那行語句的時候,編譯器在幫你做這個轉換,大約生成下面的代碼

    mov eax,[pFinal] ;final address
    mov ecx,[eax] ; vbtable address *(int *)(pFinal + pdisp)
    mov edx,eax ; save to edx
    add edx,[ecx + 4] ; ecx + 4 is (*(int *)(pFinal + pdisp) + vdisp)
    mov [pBase],edx ; edx = pFinal + mdisp + *(int *)(*(int *)(pFinal + pdisp) + vdisp) + pdisp;
    ; here mdisp = 0, pdisp = 0, vdisp = 4


   也許你要說了,我要這些東西來干什么?要轉換的時候直接轉換就好了,編譯器會幫做,的確,大多數(shù)的時候確實是這樣,但是,在某些時候卻并不如此,現(xiàn)在你 要實現(xiàn)一個功能,輸入一個指針,輸入一個 _PMD 結構,你要實現(xiàn)一個AdjustPointer 的函數(shù)來生成另一個指針.這個時候你也只能這樣完成了,因為我沒有給你兩個指針的名字,就算給了你字符串形式的名字也沒有用,呃....你也許會說,辦法 是有的,的確是有,模板就能實現(xiàn)這種功能,呵..這個我們暫時不討論具體的實現(xiàn)細節(jié).也許你要問了,究竟什么時候會去實現(xiàn)這種聽都沒有聽過的功能,其實這 個函數(shù)是真正存在的,只不過不是由你來實現(xiàn)的,而是 ms 的人實現(xiàn)的,你只用寫一個 帶有 c++ 異常的程序,使用 ida 反匯編,然后查找函數(shù),就能找到這個函數(shù)了,他用來在異常處理時創(chuàng)建 catch 所需要的 object.至于這個詳細的信息,請期待.我會最快速度寫出關于 vc 是怎樣實現(xiàn) c++ 異常的文章來.

  最后了,說說那個 _PMD 結構的獲取方式.看的時候不要吃驚,方法比較的麻煩,比如我想知道和 CFinal 類相關的 _PMD 信息,先新建工作,寫下 throw pFinal 這樣的語句,編譯,在這個語句的地方設置斷點,運行,轉到反匯編,進入 __CxxThrowException@8 函數(shù),這個時候不出意外你能看到一個叫 pThrowInfo 的東西(如果看不到,請打開"顯示符號名"選項),在 watch 窗口里面輸入pThrowInfo,展開他,看到一個pCatchableTypeArray,記錄下他的 nCacthableTypes的值,然后在 watch 里面輸入
pThrowInfo->pCatchableTypeArray->arrayOfCatchableTypes[0] 到 pThrowInfo->pCatchableTypeArray->arrayOfCatchableTypes[n], n 就是你剛剛記錄的值減1,再展開他們,你就能看到一個 thisDisplacement 的數(shù)據(jù),繼續(xù)展開就是 mdisp 等等了,很是麻煩吧.哈..你已經(jīng)猜到了,這個是和異常有關系的.

  后記: 這段時間,我一直在讀些反匯編之后的代碼,也頗有些心得,所以才有想法寫一些文章,探討 vc 編譯器鮮為人知(太過狂妄了)的秘密,這個方面的文章也有人寫過,那些文章也給我不少的啟發(fā),我不認為自己是第一個發(fā)現(xiàn)這些秘密的人,但是至少我自己知道 的,我是第一個把這些東西寫出來的人.文章里面作墨多的部分都是自己發(fā)現(xiàn)的.就這個文章里面的內(nèi)容來說,inside the c++ object model 是有比較詳細的描寫,但是他并不是轉換針對 vc 這個編譯器的實現(xiàn),而 _PMD 這個結構我也沒有在什么地方見有人描述過,只是在 windows develop network 的2002年12月的雜志上看有人提到過這個結構,可惜他卻沒有了解(至少他在他發(fā)表文章的時候是如是說的)這個結構的用處(正是因為這個原因,我才有寫 這個文章以及后續(xù)文章的沖動).所以,這個文章也算是我自己的原創(chuàng)吧.這個文件雖然和游戲制造沒有太大的關系,但是小 T 自視清高,不愿意自己的文章被一幫不懂的人評價來評價去的,所以也沒有發(fā)到那些著名的 xxx 網(wǎng)站,只發(fā) goldpoint.轉載請注明出處(小 T 對自己的第一個原創(chuàng)文章比較珍惜,比較重視,謝謝).


posted on 2007-10-14 00:37 erran 閱讀(1524) 評論(1)  編輯 收藏 引用 所屬分類: C & C++

Feedback

# re: 轉:C++對象內(nèi)存布局 2008-08-10 16:10 AlexEric
雖然不是第一次看到這方面的內(nèi)容,不過寫得還是不錯的。  回復  更多評論
  

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美精品在线观看| 一二三区精品福利视频| 午夜久久影院| 亚洲性感美女99在线| 欧美日韩成人综合在线一区二区 | 久久精品国产第一区二区三区| 国产精品五月天| 久久精品视频在线| 欧美一区午夜精品| 激情综合色综合久久综合| 香蕉成人伊视频在线观看| 亚洲精品影视在线观看| 欧美日韩中文在线观看| 午夜精品国产更新| 久久精品盗摄| 亚洲精品亚洲人成人网| 中文欧美在线视频| 一区三区视频| 亚洲美女尤物影院| 国产午夜精品理论片a级探花| 另类尿喷潮videofree | 最新国产拍偷乱拍精品| 欧美不卡高清| 欧美亚洲尤物久久| 老司机成人网| 亚洲欧美日韩直播| 美女精品一区| 欧美一区二区大片| 欧美aⅴ一区二区三区视频| 亚洲尤物在线| 欧美成年人视频网站| 欧美亚洲三级| 欧美精品97| 狼狼综合久久久久综合网| 欧美日韩精品免费观看视频| 久久精品人人爽| 欧美色偷偷大香| 欧美激情视频在线播放 | aa成人免费视频| 性做久久久久久久久| 最新高清无码专区| 欧美一区二区三区在| 中文亚洲欧美| 欧美国产欧美亚洲国产日韩mv天天看完整 | 欧美一区二区三区四区高清| 夜夜嗨av一区二区三区网站四季av| 亚洲免费视频中文字幕| 欧美a级理论片| 久久一区精品| 国产日本欧美视频| 在线亚洲精品| 一本综合精品| 欧美国产日韩免费| 欧美成年人在线观看| 国产农村妇女精品一二区| 欧美高清日韩| 亚洲精品美女免费| 久久综合给合久久狠狠色| 欧美一区二区三区日韩视频| 欧美日韩一区二区欧美激情 | 亚洲黄一区二区三区| 久久精品九九| 久久只精品国产| 国产专区欧美精品| 欧美在线欧美在线| 久久久精品动漫| 韩国福利一区| 久久中文欧美| 欧美国产综合视频| 亚洲人成在线播放| 欧美国产第一页| 最新成人av网站| 99综合视频| 欧美三日本三级三级在线播放| 亚洲精品综合久久中文字幕| 一区二区高清在线| 欧美午夜无遮挡| 亚洲在线观看免费| 久久另类ts人妖一区二区| 欧美在线亚洲在线| 久久综合九色99| 亚洲国产裸拍裸体视频在线观看乱了中文 | 日韩亚洲精品电影| 亚洲无人区一区| 国产精品久久波多野结衣| 亚洲一区图片| 另类av一区二区| 99re热精品| 亚洲激情视频在线播放| 久久经典综合| 国产精品久久一卡二卡| 欧美一区二区三区免费观看视频| 久久这里只精品最新地址| 亚洲日本成人| 国产精品久久久久久久午夜| 久久国产精品毛片| 亚洲国产精品ⅴa在线观看| 亚洲视频网在线直播| 国产亚洲一二三区| 欧美精品www| 午夜久久久久久| 亚洲第一天堂av| 欧美一区午夜视频在线观看| 在线播放精品| 久热综合在线亚洲精品| 日韩网站在线| 国外成人免费视频| 欧美日韩另类综合| 久久久久久网| 亚洲天堂成人在线观看| 欧美69wwwcom| 欧美一区二区三区男人的天堂| 在线精品视频免费观看| 欧美系列一区| 欧美激情亚洲视频| 欧美制服丝袜第一页| 日韩午夜高潮| 亚洲大胆人体在线| 久久久精品2019中文字幕神马| 一区二区三区波多野结衣在线观看| 国产在线国偷精品产拍免费yy| 欧美精品一区三区在线观看| 性色av一区二区三区红粉影视| 亚洲国产精品欧美一二99| 久久精品欧美日韩| 欧美一区2区三区4区公司二百| 日韩视频在线免费观看| 激情亚洲一区二区三区四区| 国产精品青草久久| 欧美绝品在线观看成人午夜影视| 久久久噜噜噜久久狠狠50岁| 午夜日韩福利| 亚洲一区制服诱惑| 一本一本a久久| 亚洲精选久久| 亚洲精品免费在线播放| 欧美激情国产日韩精品一区18| 久久久久久亚洲精品杨幂换脸| 亚洲一区二区三区国产| 一区二区激情| 一区二区三区高清在线 | 国产精品激情电影| 欧美日韩免费一区二区三区| 欧美日本三级| 欧美日韩mp4| 欧美日韩国产一中文字不卡| 欧美日韩国产专区| 欧美另类视频在线| 欧美涩涩视频| 国产精品成人国产乱一区| 欧美视频第二页| 国产精品久久久爽爽爽麻豆色哟哟| 欧美午夜精品久久久久久超碰| 欧美日韩综合网| 国产精品久久99| 国产色产综合色产在线视频| 国产一区在线播放| 在线日韩中文| 亚洲精品午夜精品| 亚洲欧美国产不卡| 久久精品二区三区| 免费成人在线视频网站| 亚洲二区免费| 中国成人黄色视屏| 欧美一级在线视频| 欧美一区国产在线| 国产精品美女在线| 在线视频亚洲一区| 夜夜爽av福利精品导航| 另类专区欧美制服同性| 亚洲女人天堂av| 午夜一区二区三区在线观看| 欧美日韩国产综合在线| 亚洲理论在线观看| 日韩亚洲欧美在线观看| 久久成人精品视频| 欧美日韩国产成人高清视频| 欧美激情一区二区三级高清视频 | 欧美一区二区三区播放老司机 | 欧美在线一二三四区| 国产日韩久久| 亚洲人www| 欧美一区二区网站| 亚洲成人资源网| 一本色道久久综合一区| 欧美一区永久视频免费观看| 欧美大片一区二区| 亚洲欧美日本另类| 欧美激情按摩在线| 国产亚洲精品久| 一本色道**综合亚洲精品蜜桃冫 | 91久久久国产精品| 欧美在线免费视频| 国产精品成人免费视频| 亚洲激情视频网| 欧美在线观看日本一区| 亚洲精品一区二区三| 久久久久久久97| 国产欧美一区二区色老头| 一区二区免费在线视频|