• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • C 對象布局及多態實現探索之虛繼承

    發表于:2007-05-25來源:作者:點擊數: 標簽:之虛布局多態探索對象
    下面我們來看虛繼承。首先看看這C020類,它從C010虛繼承:} struct C010 { C010() : c_(0x01) {} void foo() { c_ = 0x02; } char c_; }; struct C020 : public virtual C010 { C020() : c_(0x02) {} char c_; }; 運行如下代碼,查看對象的內存布局: PRINT_
    下面我們來看虛繼承。首先看看這C020類,它從C010虛繼承:}

    struct C010
    {
     C010() : c_(0x01) {}
     void foo() { c_ = 0x02; }
     char c_;
    };
    struct C020 : public virtual C010
    {
     C020() : c_(0x02) {}
     char c_;
    };
      運行如下代碼,查看對象的內存布局:

    PRINT_SIZE_DETAIL(C020)
      結果為:

    The size of C020 is 6
    The detail of C020 is c0 c2 45 00 02 01
      很明顯對象的起始處是一個指針,然后是子類的成員變量,接下來是父類的成員變量。和以前的討論不同的是由于使用了虛繼承,父類的成員變量被放到了最后面。

      運行如下的代碼:

    C020 c020;
    c020.C010::c_ = 0x04;
      由于子類中的變量和父類中的變量重名,所以我們必須用這種方式來訪問屬于父類的成員變量,普通情況下不需要這種寫法。我們看看后面這行代碼對應的匯編代碼:

    0042387E mov eax,dword ptr [ebp FFFFF82Ch]
    00423884 mov ecx,dword ptr [eax 4]
    00423887 mov byte ptr [ebp ecx FFFFF82Ch],4
      前面說過對象的起始是一個指針,第1行指令取到這個指針的值,第2行把這個指針指向的地址后移4字節后的值(做為一個4字節的值)取出來。執行完這句我們看看ecx寄存器,可知取出來的值為5。最后一行是真正的賦值指令,它通過在對象的起始處(即[ebp FFFFF32Ch])加上ecx中的值做偏移值(即5)來得到賦值的目的地址。接合前面的對象布局輸出,我們可以發現從對象起始地址開始加5字節的偏移值,剛好得到父類的成員變量的地址。這樣我們可以大致分析出直接虛繼承的子類的對象布局。

    |子類5            |父類1   ?。?br/> |偏移值指針4,5|子類成員變量1|父類成員變量1|

      (注:第一個數字為所在區域的長度(字節數),偏移值指針后的第二個數字為該指針指向的偏移值。后同。)

      通過查看內存可以發現偏移值指針指向的內存前4字節為0,我不知道它的具體的用途是什么。接下來的4字節是一個32位的整數,也就是真正的偏移值。即從子類的起始位置到被虛繼承的父類的起始位置的偏移值,在我們前面的例子中這個值為5(一個指針加一個char成員變量)。

      通過這個分析我們可以看到在虛承繼的情況下,通過子類的對象訪問父類的普通成員變量的效率是相當低的。如果必須用到虛繼承,也應該盡量不要在父類中放置普通成員變量(靜態成員變量不受影響)。

      另外為什么微軟不把偏移值直接放到子類中,而是采用偏移值指針。我想是因為采用指針的方式更為靈活,即使以后需要擴展也不影響類對象的布局。
      按下來我們再看看這幾行代碼:

    PRINT_OBJ_ADR(c020);
    C010 * pt = &c020;
    PRINT_PT(pt);
    pt->c_ = 0x03;
      第2行聲明了一個父類指針,并讓它指向一個子類的對象。第3行打印出這個指針的值。運行結果為:

    c020's address is : 0012F708
    pt's value is : 0012F70D
      我們可以看到賦值后的指針的值并不等于賦給它的對象地址值。也就是說在這個賦值過程中編譯器進行了額外的工作,即調整了指針的值。我們看看第2行對應的匯編代碼,看看編譯器究竟做了些什么?

    01 004238EA lea eax,[ebp FFFFF82Ch]
    02 004238F0 test eax,eax
    03 004238F2 jne 00423900
    04 004238F4 mov dword ptr [ebp FFFFF014h],0
    05 004238FE jmp 00423916
    06 00423900 mov ecx,dword ptr [ebp FFFFF82Ch]
    07 00423906 mov edx,dword ptr [ecx 4]
    08 00423909 lea eax,[ebp edx FFFFF82Ch]
    09 00423910 mov dword ptr [ebp FFFFF014h],eax
    10 00423916 mov ecx,dword ptr [ebp FFFFF014h]
    11 0042391C mov dword ptr [ebp FFFFF820h],ecx
      喔!比想象的要復雜的多。一行簡單的指針賦值語句卻產生了這么多的匯編代碼。這行代碼本身的語義是取對象的地址賦給一個指針,對于編譯器來說它把這做為指針到指針的賦值來處理。由于牽涉到了向上的類型轉換,同時又有虛繼承存在。根據前面的布局分析,在虛繼承的情況下,父類位于對象布局的后部。因此在這里要做一個指針位置的調整。由于調整要根據源指針來進行計算,所以先要對源指針的合法性進行檢查,以避免運行時的指針異常錯誤。前3行的匯編指令就是在做這件事,檢查源指針是否為NULL。如果為NULL則執行4、5、10、11行,最終給pt賦0。如果不為NULL跳至第6行執行到最后。重要的是第6、7、8行代碼,它們通過偏移值指針找到偏移值,并以此來調整指針的位置,讓目的指針最終指向對象中的父類部分的數據成員。

      對比一下普通的指針賦值,我們可以對上面賦值的復雜性和低效有更深的認識。

    C010 * pt1 = NULL;
    C010 * pt2 = pt1;
      這兩行相應的匯編代碼為:

    0042397D mov dword ptr [ebp FFFFF814h],0
    00423987 mov eax,dword ptr [ebp FFFFF814h]
    0042398D mov dword ptr [ebp FFFFF808h],eax
      第1行是普通的賦值,編譯器并不做任何的檢查,即使源指針為NULL。因為它不需要根據源指針(本處為NULL)做任何計算。第2個賦值也很直接,只是通過eax做了一個中轉。這里我們就可以看到前面的虛繼承下的子類指針到父類指針的賦值是我么的低效。在程序中應盡量的避免這種代碼。

    原文轉自:http://www.kjueaiud.com

    評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>