• <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>

            runsisi

              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
              45 隨筆 :: 15 文章 :: 26 評論 :: 0 Trackbacks

            n久沒有寫過了,轉載一篇,呵呵。不喜歡轉載,但這篇文章確實還不錯,只是不知道為什么找不到原文出處。
            以下為轉載全文。修改了一些細節。對cppblog崩潰了,這是啥所見即所得排版啊,唉,暈倒。

            這幾天寫的程序應用到多繼承。

            以前對多繼承的概念非常清晰,可是很久沒用就有點模糊了。重新研究一下,刷新下記憶。

            假設我們有下面的代碼:

            #include <stdio.h>

            class A
            {
            private:
               char data;
            public:
               A(){data = 'A';}
               virtual void Show(){printf("A\n");};
               virtual void DispA(){printf("a\n");};
            };

            class B
            {
            private:
               int data;
            public:
               B(){data = 'B';}
               virtual void Show(){printf("B\n");};
               virtual void DispB(){printf("b\n");};
            };

            class C
            {
            private:
               char data;
            public:
               C(){data = 'C';}
               virtual void Show(){printf("C\n");};
               virtual void DispC(){printf("c\n");};
            };

            class D : public A, public B, public C
            {
            public:
               char data;
            public:
               D(){data = 'D';}
               virtual void Show(){printf("D\n");};
               virtual void DispD(){printf("d\n");};
            };

            class E : public D
            {
            private:
               char data;
            public:
               E(){data = 'E';}
               virtual void Show(){printf("E\n");};
               virtual void DispE(){printf("e\n");};
            };

            int main()
            {
               D *d = new D;
               A *a = (A*)d;
               B *b = (B*)d;
               C *c = (C*)d;;

               d->Show();
               a->Show();
               b->Show();

               a->DispA();
               b->DispB();
               d->DispD();

               D *d1 = (D*)a;
               d1->Show();
               d1->DispD();
               D *d2 = (D*)b;
               d2->Show();
               d2->DispD();

               char x = d->data;
               return 0;
            }

            每個類都有兩個虛擬函數Show()DispX()。類A,B,C是基本類,而D是多繼承,最后E又繼承了D。那么對于類E,它的內存映像是怎樣的呢?為了解答這個問題,我們回顧一下基本類的內存映像:

            + --------------+ <- this
            +    VTAB       +
            + --------------+
            +               +
            +    Data       +
            +               +
            + --------------+

            如果一個類有虛擬函數,那么它就有虛函數表(VTAB)。類的第一個單元是一個指針,指向這個虛函數表。如果類沒有虛函數,并且它的祖先(所有父類)均沒有虛函數,那么它的內存映像和C的結構一樣。所謂虛函數表就是一個數組,每個單元指向一個虛函數地址。
            如果類Y是類X的一個繼承,那么類Y的內存映像如下:

            + --------------+ <- this
            +   Y
            VTAB   +
            + --------------+
            +               +
            +   X
            Data   +
            +               +
            + --------------+
            +               +
            +   Y
            Data   +
            +               +
            + --------------+
            Y
            的虛函數表基本和X的相似。如果Y有新的虛函數,那么就在VTAB的末尾加上一個。如果Y重新定義了原有的虛函數,那么原的指針指向新的函數入口。這樣無論是內存印象和虛函數表,Y都和X兼容。這樣當執行 X* x = (Y*)y;之后,x可以很好的被運用,并且可以享受新的虛擬函數。

            現在看多重繼承:
            class D : public A, public B, public C
            {
               ....
            }
            它的內存映像如下
            :  
            + --+ -----------------+ 00H <- this
            +   +    D
            VTAB     +
            + A + -----------------+ 04H
            +   +    A
            的 數據
                 +
            + --+ -----------------+ 08H
            +   +    B
            VTAB'    +
            + B + -----------------+ 0CH
            +   +    B
            的 數據
                 +
            + --+ -----------------+ 10H
            +   +    C
            VTAB'    +
            + C + -----------------+ 14H
            +   +    C
            的 數據
                 +
            + --+ -----------------+ 18H
            + D +    D
            的 數據
                 +
            + --+ -----------------+
            (因為對齊于雙字,A~D的數據雖然只是一個char,但需要對齊到DWORD,所以占4字節)

            對于A,它和單繼承沒有什么兩樣。BC被簡單地放在A的后面。如果它們虛函數在D中被重新定義過(比如Show函數),那么它們需要使用新的VTAB,使被重定義的虛函數指到正確的位置上(這對于COM或類似的技術是至關重要的)。最后,D的數據被放置到最后面。
            對于E的內存映像問題就可以不說自明了。

            下面我們看一下C++是如何處理
               D *d;
               ......
               B *b = (B*)d;
            這樣的要求的。設置斷點,進入反匯編,你可以看到如下的匯編代碼:

            B *b = (B*)d;
            00401062  cmp         dword ptr [d],0
            00401066  je          main+73h (401073h)
            00401068  mov         eax,dword ptr [d]
            0040106B  add         eax,8
            0040106E  mov         dword ptr [ebp-38h],eax
            00401071  jmp         main+7Ah (40107Ah)
            00401073  mov         dword ptr [ebp-38h],0
            0040107A  mov         ecx,dword ptr [ebp-38h]
            0040107D  mov         dword ptr [b],ecx
            從上述匯編代碼可以看出:如果源(這里是d)是NULL,那么目標(這里是b)也將被置為NULL,否則目標將指向源的地址并向下偏移8個字節,正好就是上圖所示BVTAB位置。至于為什么要用ebp-38h作緩存,這是編譯器的算法問題了。等以后有時間再研究。

            接下來看一個比較古怪的問題,這個也是我寫這篇文章的初衷:
            根據上面的多繼承定義,如果給出一個類B的實例b,我們是否可以求出D的實例?

            為什么要問這個問題。因為存在下面的可能性:
            class B
            {
               ...
               virtual int GetTypeID()=0;
               ...
            };

            class D : public A, public B, public C
            {
               ...
               virtual int GetTypeID(){return 0;};
               ...
            };

            class Z : public X, public Y, public B
            {
               ...
               virtual int GetTypeID(){return 1;};
               ...
            };

            void MyFunc(B* b)
            {
               int t = b->GetTypeID();
               switch(t)
               {
               case 0:
                   DoSomething((D*)b); //
            可能嗎?

                   break;
               case 1:
                   DoSomething((Z*)b); //
            可能嗎?

                   break;
               default:
                   break;
               }
            }

            猛一看很值得懷疑。但仔細想想,這是可能的,事實也證明了這一點。因為編譯器了解這DB這兩個類相互之間的關系(也就是偏移量),因此它會做相應的轉換。同樣,設置斷點,查看匯編:
            D *d2 = (D*)b;
            00419992  cmp         dword ptr [b],0
            00419996  je          main+196h (4199A6h)
            00419998  mov         eax,dword ptr [b]
            0041999B  sub         eax,8
            0041999E  mov         dword ptr [ebp-13Ch],eax
            004199A4  jmp         main+1A0h (4199B0h)
            004199A6  mov         dword ptr [ebp-13Ch],0
            004199B0  mov         ecx,dword ptr [ebp-13Ch]
            004199B6  mov         dword ptr [d2],ecx
            如果源(這里是b)為NULL,那么目標(這里是d2)也為NULL。否則目標取源的地址并向上偏移8個字節,這樣正好指向D的實例位置。同樣,為啥需要ebp-13Ch做緩存,待查。

            前一段時間因為擔心.NET中將interface轉成相應的類會有問題。今天對C++多重繼承的復習徹底消除了疑云。

             

             

            posted on 2010-09-08 14:14 runsisi 閱讀(208) 評論(0)  編輯 收藏 引用
            久久人人爽爽爽人久久久| 91久久精一区二区三区大全| 99久久精品国产毛片| 国产成人精品久久| 一本久久免费视频| 国产精品无码久久久久久| A级毛片无码久久精品免费| 久久人人爽人人爽人人片AV麻豆| 久久久久亚洲av成人网人人软件| 久久久噜噜噜www成人网| 久久av高潮av无码av喷吹| 99久久精品免费看国产一区二区三区| 国产亚洲色婷婷久久99精品| 欧美精品一区二区久久| 久久本道伊人久久| 久久久久亚洲AV成人网人人网站| 狠狠色丁香婷婷久久综合不卡| 一级女性全黄久久生活片免费| 99久久精品午夜一区二区| 一级女性全黄久久生活片免费| 国产精品久久自在自线观看| 亚洲精品美女久久久久99| 麻豆久久| 久久亚洲天堂| 国产99久久九九精品无码| 99久久精品毛片免费播放| 亚洲精品乱码久久久久久按摩| 日韩AV毛片精品久久久| 国产精品狼人久久久久影院| 久久99精品国产一区二区三区| 伊人久久久AV老熟妇色| 亚洲日本久久久午夜精品| 亚洲国产成人久久综合一| 99国产精品久久久久久久成人热| 97精品依人久久久大香线蕉97| 久久精品综合网| 噜噜噜色噜噜噜久久| 久久久国产打桩机| 国产成人精品综合久久久久| 久久久久波多野结衣高潮| 国产成人精品综合久久久久|