• <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++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
              45 隨筆 :: 15 文章 :: 26 評論 :: 0 Trackbacks

            n久沒有寫過了,轉(zhuǎn)載一篇,呵呵。不喜歡轉(zhuǎn)載,但這篇文章確實還不錯,只是不知道為什么找不到原文出處。
            以下為轉(zhuǎn)載全文。修改了一些細節(jié)。對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;
            }

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

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

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

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

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

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

            下面我們看一下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個字節(jié),正好就是上圖所示BVTAB位置。至于為什么要用ebp-38h作緩存,這是編譯器的算法問題了。等以后有時間再研究。

            接下來看一個比較古怪的問題,這個也是我寫這篇文章的初衷:
            根據(jù)上面的多繼承定義,如果給出一個類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這兩個類相互之間的關系(也就是偏移量),因此它會做相應的轉(zhuǎn)換。同樣,設置斷點,查看匯編:
            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個字節(jié),這樣正好指向D的實例位置。同樣,為啥需要ebp-13Ch做緩存,待查。

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

             

             

            posted on 2010-09-08 14:14 runsisi 閱讀(207) 評論(0)  編輯 收藏 引用
            久久这里有精品| 国产女人aaa级久久久级| 久久久亚洲AV波多野结衣| 国产A级毛片久久久精品毛片| 亚洲伊人久久精品影院| 国产精品久久久久影院嫩草| 91精品无码久久久久久五月天| 久久影视综合亚洲| 成人国内精品久久久久影院| 久久精品国产精品亚洲下载| 久久精品国产日本波多野结衣| 久久精品国产亚洲综合色| 久久有码中文字幕| 久久久久久久99精品免费观看| 一本大道久久东京热无码AV| 久久综合久久综合久久| 中文字幕热久久久久久久| 爱做久久久久久| 久久亚洲春色中文字幕久久久 | 久久精品国产精品国产精品污 | 国产成人无码久久久精品一| 免费精品久久久久久中文字幕 | 亚洲精品午夜国产va久久| 91超碰碰碰碰久久久久久综合| 亚洲国产欧美国产综合久久 | 国内精品久久久久影院一蜜桃| 亚洲国产香蕉人人爽成AV片久久| 久久精品国产99国产电影网| 色综合久久久久无码专区 | 久久激情五月丁香伊人| 国内精品久久久久影院日本| 久久久久亚洲AV片无码下载蜜桃 | 综合久久国产九一剧情麻豆| 亚洲国产成人精品女人久久久| 久久久久这里只有精品 | 亚洲精品高清一二区久久| 久久精品视频91| 久久久久亚洲精品无码网址| 丁香久久婷婷国产午夜视频| 国产成人无码精品久久久久免费| 国产成人精品久久亚洲高清不卡 |