• <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)  編輯 收藏 引用
            久久国产乱子伦精品免费强| 国产精品内射久久久久欢欢| 久久亚洲国产最新网站| 热久久最新网站获取| 99精品国产综合久久久久五月天| 日日躁夜夜躁狠狠久久AV| 2020久久精品国产免费| 久久国产综合精品五月天| 亚洲人成精品久久久久| 亚洲国产成人久久综合一 | 久久久久久毛片免费播放| 久久久久久久综合日本亚洲| 久久人人爽人爽人人爽av| 久久久一本精品99久久精品88| 久久久久亚洲精品中文字幕| 日产精品久久久一区二区| 性做久久久久久久久老女人| 久久精品国产99久久久| 久久这里只精品99re66| 精品久久久无码中文字幕| 国产精品99久久久久久人| 7777精品久久久大香线蕉| 伊人久久无码精品中文字幕| 久久精品无码av| 久久久久久狠狠丁香| 国产99久久精品一区二区| 久久亚洲欧美国产精品| 久久国内免费视频| 亚洲精品美女久久久久99小说| 久久人人爽人人精品视频| 国产午夜精品理论片久久| 亚洲国产精品久久66| 91精品国产高清久久久久久国产嫩草 | 久久久久久国产精品无码下载 | 日产久久强奸免费的看| 久久国产精品国产自线拍免费| 久久亚洲精品无码AV红樱桃| 无码国内精品久久人妻| 国内精品久久久久影院一蜜桃| 久久天天躁狠狠躁夜夜avapp| 色综合久久中文字幕无码|