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

chaosuper85

C++博客 首頁 新隨筆 聯系 聚合 管理
  118 Posts :: 0 Stories :: 3 Comments :: 0 Trackbacks

  C++虛函數探索筆記(2)——虛函數與多繼承

    關注問題:

    虛函數的作用

    虛函數的實現原理

    虛函數表在對象布局里的位置

    虛函數的類的sizeof

    純虛函數的作用

    多級繼承時的虛函數表內容

    虛函數如何執行父類代碼

    多繼承時的虛函數表定位,以及對象布局

    虛析構函數的作用

    虛函數在QT的信號與槽中的應用

    虛函數與inline修飾符,static修飾符

    前面我們嘗試了一個簡單的例子,接下來嘗試一個多級繼承的例子,以及一個多繼承的例子。主要涉及到以下問題:多級繼承時虛函數表的內容是如何填寫的,如何在多級繼承的情況下調用某一級父類里的虛函數,以及在多繼承(多個父類)的情況下的對象布局。

    多級繼承

    在這里,多級繼承指的是有3層或者多層繼承關系的情形。讓我們看看下面的代碼:
 //Source filename: Win32Con.cpp

 #include <iostream>
 using namespace std;
class parent1
 {
public:
    virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};
    virtual int fun2()=0;
};
class child1:public parent1
 {
public:

    virtual int fun1()
    {
        cout<<"child1::fun1()"<<endl;
        parent1::fun1();
        return 0;
    }
    virtual int fun2()
    {
        cout<<"child1::fun2()"<<endl;
        return 0;
    }
};

class grandson:public child1
 {
public:
    virtual int fun2()
    {
        cout<<"grandson::fun2()"<<endl;
        //parent1::fun2();
        parent1::fun1();
        child1::fun2();
        return 0;
    }
};

void test_func1(parent1 *pp)
{
    pp->fun1();
    pp->fun2();
}

int main(int argc, char* argv[])
{
    grandson sunzi;
    test_func1(&sunzi);
    return 0;

 


    這段代碼展示了三個class,分別是parent1,child1,grandson.

    類parent1定義了兩個虛函數,其中fun2是一個純虛函數,這個類是一個不可實例化的抽象基類。

    類child1繼承了parent1,并且對兩個虛函數fun1和fun2都編寫了實現的代碼,這個類可以被實例化。

    類grandson繼承了child1,但是只對虛函數fun2編寫了實現的代碼。

    此外,我們還改寫了test_func1函數,它的參數為parent1類型的指針,我們可以將parent1的子孫類作為這個函數的參數傳入。在這個函數里,我們將依次調用parent1類的兩個虛函數。

    可以先通過閱讀代碼預測一下程序的輸出內容。

    程序的輸出內容將是:

    child1::fun1()

    parent1::fun1()

    grandson::fun2()

    parent1::fun1()

    child1::fun2()

    先看第一行輸出child1::fun1(),為什么會輸出它呢?我們定義的具體對象sunzi是grandson類型的,test_func1的參數類型是parent1類型。在調用這個虛函數的時候,是完成了一次怎樣的調用過程呢?

    讓我們再次使用cl命令輸出這幾個類的對象布局:
 class parent1   size(4):
        +---
 0      | {vfptr}
        +---

parent1::$vftable@:
        | &parent1_meta
        |  0
 0      | &parent1::fun1
 1      | &parent1::fun2

parent1::fun1 this adjustor: 0
parent1::fun2 this adjustor: 0

class child1    size(4):
        +---
        | +--- (base class parent1)
 0      | | {vfptr}
        | +---
        +---

child1::$vftable@:
        | &child1_meta
        |  0
 0      | &child1::fun1
 1      | &child1::fun2

child1::fun1 this adjustor: 0
child1::fun2 this adjustor: 0

class grandson  size(4): //grandson的對象布局
        +---
        | +--- (base class child1)
        | | +--- (base class parent1)
 0      | | | {vfptr}
        | | +---
        | +---
        +---

grandson::$vftable@:  //grandson虛函數表的內容
        | &grandson_meta
        |  0
 0      | &child1::fun1
 1      | &grandson::fun2

grandson::fun2 this adjustor: 0

 


    因為我們實例化的是一個grandson對象,讓我們看看它的對象布局。正如前面的例子一樣,里面只有一個vfptr指針,但是不一樣的卻是這個指針所指的虛函數表的內容:

    第一個虛函數,填寫的是child1類的fun1的地址;第二個虛函數填寫的才是grandson類的fun2的地址。

    很顯然我們可以得出這樣一個結論:在一個子對象的虛函數表里,每一個虛函數的實際運行的函數地址,將填寫為在繼承體系里最后實現該虛函數的函數地址。

    所以當我們在test_func1里調用了傳入的parent1指針的fun1函數的時候,我們實際執行的是填寫在虛函數表里的child1::fun1(),而調用fun2函數的時候,是從虛函數表里得到了grandson::fun2函數的地址并調用之。在“程序輸出結果”表里的第一行和第三行結果證實了上述結論。

    再看一下程序代碼部分的child1::fun1()的實現代碼,在第18行,我們有parent1::fun1();這樣的語句,這行代碼輸出了運行結果里的第二行,而在grandson::fun2()的實現代碼第35行的parent1::fun1();以及第36行的child1::fun2();則輸出了運行結果里的第四行和第五行的內容。這三行代碼展示了如何調用父類以及更高的祖先類里的虛函數。——事實上,這與調用父類的普通函數沒有任何區別。

    在程序代碼的第34行,有一行被注釋了的內容//parent1::fun2();,之所以會注釋掉,是因為這樣的代碼是無法通過編譯的,因為在parent1類里,fun2是一個“純虛函數”也就是說這個函數沒有代碼實體,在編譯的時候,鏈接器將無法找到fun2的目標代碼從而報錯。

    其實有了對虛函數的正確的認識,上面的多級繼承是很自然就能明白的。然而在多繼承的情況下,情況就有所不同了……

    多繼承下虛函數的使用

    假如一個類,它由多個父類繼承而來,而在不同的父類的繼承體系里,都存在虛函數的時候,這個類的對象布局又會是怎樣的?它又是怎樣定位虛函數的呢?

    讓我們看看下面的代碼:
 //Source filename: Win32Con.cpp
 #include <iostream>
 using namespace std;
class parent1
 {
public:
    virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};
};
class parent2
 {
public:
    virtual int fun2(){cout<<"parent2::fun2()"<<endl;return 0;};
};
class child1:public parent1,public parent2
 {
public:
    virtual int fun1()
    {
        cout<<"child1::fun1()"<<endl;
        return 0;
    }
    virtual int fun2()
    {
        cout<<"child1::fun2()"<<endl;
        return 0;
    }
};
void test_func1(parent1 *pp)
{
    pp->fun1();
}
void test_func2(parent2 *pp)
{
    pp->fun2();
}
int main(int argc, char* argv[])
{
    child1 chobj;
    test_func1(&chobj);
    test_func2(&chobj);
    return 0;
}
 


    這一次,我們有兩個父類,parent1和parent2,在parent1里定義了虛函數fun1,而在parent2里定義了虛函數fun2,然后我們有一個子類child1,在里面重新實現了fun1和 fun2兩個虛函數。然后我們編寫了test_func1函數來調用parent1類型對象的fun1函數,編寫了test_func2函數調用parent2對象的fun2函數。在main函數里我們實例化了一個child1類型的對象chobj,然后分別傳給test_func1和test_func2去執行。

    這段代碼的運行結果非常簡單就能看出來:

    child1::fun1()

    child1::fun2()

    但是,讓我們看看對象布局吧:
 class child1    size(8):
        +---
        | +--- (base class parent1)
 0      | | {vfptr}
        | +---
        | +--- (base class parent2)
 4      | | {vfptr}
        | +---
        +---

child1::$vftable@parent1@:
        | &child1_meta
        |  0
 0      | &child1::fun1

child1::$vftable@parent2@:
        | -4
 0      | &child1::fun2

child1::fun1 this adjustor: 0
child1::fun2 this adjustor: 4

 


    注意到沒?在child1的對象布局里,出現了兩個vfptr指針!

    這兩個虛函數表指針分別繼承于parent1和parent2類,分別指向了不同的兩個虛函數表。

    問題來了,當我們使用test_func1調用parent1類的fun1函數的時候,調用個過程還比較好理解,可以從傳入的地址參數取得繼承自parent1的vfptr,從而執行正確的fun1函數代碼,但是當我們調用test_func2函數的時候,為什么程序可以自動取得來自parent2的vfptr呢,從而得出正確的fun2函數的地址呢?

    其實,這個工作是編譯器自動根據實例的類型完成的,在編譯階段就已經確定了在調用test_func2的時候,傳入的this指針需要增加一定的偏移(在這里則是第一個vfptr所占用的大小,也就是4字節)。

    我們可以看看main函數里這部分代碼的反匯編代碼:
   child1 chobj;
00F5162E 8D 4D F4         lea         ecx,[chobj]
00F51631 E8 F5 FB FF FF   call        child1::child1 (0F5122Bh)
    test_func1(&chobj);
00F51636 8D 45 F4         lea         eax,[chobj]
00F51639 50               push        eax
00F5163A E8 6F FB FF FF   call        test_func1 (0F511AEh)
00F5163F 83 C4 04         add         esp,4
    test_func2(&chobj);
00F51642 8D 45 F4         lea         eax,[chobj]
00F51645 85 C0            test        eax,eax
00F51647 74 0E            je          main+47h (0F51657h)
00F51649 8D 4D F4         lea         ecx,[chobj]
00F5164C 83 C1 04         add         ecx,4
00F5164F 89 8D 2C FF FF FF mov         dword ptr [ebp-0D4h],ecx
00F51655 EB 0A            jmp         main+51h (0F51661h)
00F51657 C7 85 2C FF FF FF 00 00 00 00 mov         dword ptr [ebp-0D4h],0
00F51661 8B 95 2C FF FF FF mov         edx,dword ptr [ebp-0D4h]
00F51667 52               push        edx
00F51668 E8 F6 FA FF FF   call        test_func2 (0F51163h)
00F5166D 83 C4 04         add         esp,4
    return 0;


    從第4行至第5行,執行的是test_func1函數,this指針指向 chobj (第2行lea ecx,[chobj]),但是調用test_func2函數的時候,this指針被增加了4(第14行)!于是,在test_func2執行的時候,就可以從&chobj+4的地方獲得vfptr指針,從而根據parent2的對象布局得到了fun2的地址并執行了。

    為了證實這點,我們可以將代碼做如下的修改:
     1:  int main(int argc, char* argv[])

    2:  {

    3:      child1 chobj;

    4:      test_func1(&chobj);

    5:      test_func2((parent2 *)(void *)&chobj);

    6:      return 0;

    7:  }

    8:
 


    請注意紅色部分的變化,在講chobj傳入給test_func2之前,先用(void *)強制轉換為無類型指針,再轉換為parent2 指針,這樣的轉換,顯然是可行的,因為chobj本身就是parent2的子類,然而,程序的執行效果卻是:

    child1::fun1()

    child1::fun1()

    執行test_func2函數,調用的是parent2::fun2,但是居然執行的是child1::fun1()函數!!!

    這中間發生了些什么呢?我們再看看反匯編的代碼:
   child1 chobj;
013D162E 8D 4D F4         lea         ecx,[chobj]
013D1631 E8 F5 FB FF FF   call        child1::child1 (13D122Bh)
    test_func1(&chobj);
013D1636 8D 45 F4         lea         eax,[chobj]
013D1639 50               push        eax
013D163A E8 6F FB FF FF   call        test_func1 (13D11AEh)
013D163F 83 C4 04         add         esp,4
    test_func2((parent2*)(void *)&chobj);
013D1642 8D 45 F4         lea         eax,[chobj]
013D1645 50               push        eax
013D1646 E8 18 FB FF FF   call        test_func2 (13D1163h)
013D164B 83 C4 04         add         esp,4
    return 0;
 


    從調用test_func2的反匯編代碼可以看到,這一次ecx寄存器的值沒有做改變!所以在執行test_func2的時候,將取得parent1對象布局里的vfptr,而這個vfptr所指的虛函數表里的第一項就是fun1,并且被填寫為child1::fun1的地址了。所以才出現了child::fun1的輸出內容!顯然這里有一個隱藏的致命問題,加入parent1和parent2的第一個虛函數的參數列表不一致,這樣的調用顯然就會導致堆棧被破壞掉,程序99%會立即崩潰。之前的程序沒有崩潰并且成功輸出內容,不過是因為parent1::fun1()和parent2::fun2()的參數列表一致的關系而已。

    所以,千萬不要在使用一個多繼承對象的時候,將其類型信息丟棄,編譯器還需要依靠正確的類型信息,在使用虛函數的時候來得到正確的匯編代碼!

 

 

posted on 2009-08-05 17:45 chaosuper 閱讀(186) 評論(0)  編輯 收藏 引用

只有注冊用戶登錄后才能發表評論。
網站導航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            亚洲日本成人网| 国模吧视频一区| 亚洲视频综合在线| 欧美高清在线| 久久精品国产清自在天天线| 亚洲欧美日韩国产精品| 欧美日本免费| 亚洲国产欧美在线人成| 亚洲一卡久久| 欧美jizz19性欧美| 国产亚洲成av人片在线观看桃 | 激情综合激情| 亚洲精品视频在线看| 欧美a级大片| 亚洲夫妻自拍| 牛人盗摄一区二区三区视频| 一区二区三区欧美在线| 久久精品电影| 亚洲精品一区在线| 日韩视频一区二区三区| 亚洲国产精品视频一区| 亚洲在线观看| 一区二区冒白浆视频| 亚洲国产1区| 在线成人中文字幕| 国产一区导航| 国产精品久久久久毛片大屁完整版 | 一本久道久久综合婷婷鲸鱼| 欧美一级片久久久久久久| 亚洲欧洲一区二区在线观看| 韩日午夜在线资源一区二区| 91久久精品www人人做人人爽 | 欧美日韩在线第一页| 国产综合久久| 欧美日韩一区二区精品| 国产日韩精品一区观看| 黄色成人av在线| 午夜精品久久久久| 中文精品视频一区二区在线观看| 亚洲国产高潮在线观看| 亚洲国产欧美日韩另类综合| 老鸭窝毛片一区二区三区| 最新日韩精品| 午夜精品国产更新| 亚洲一二区在线| 欧美激情二区三区| 国内成+人亚洲+欧美+综合在线| 在线欧美福利| 久久久国产精品一区| 欧美与黑人午夜性猛交久久久| 久久国产精品一区二区三区四区| 欧美日韩综合另类| 亚洲日本欧美| 老**午夜毛片一区二区三区| 亚洲一区激情| 国产欧美日韩另类视频免费观看| 日韩视频二区| 亚洲欧洲日产国产网站| 亚洲欧美日韩中文视频| 国模大胆一区二区三区| 性欧美超级视频| 一本一本久久| 欧美日韩国产小视频在线观看| 激情成人中文字幕| 亚洲激情偷拍| 亚洲国产精品成人精品| 久久全球大尺度高清视频| 亚洲精品中文字幕女同| 欧美在线精品免播放器视频| 激情六月综合| 国产日韩欧美在线| 一本综合精品| 久久大综合网| 欧美日本一区| 99视频精品免费观看| 亚洲精品视频在线观看免费| 欧美日韩中文字幕日韩欧美| 最新国产精品拍自在线播放| 亚洲欧美日本国产有色| 欧美日韩一区二区三区四区在线观看 | 欧美精品在线一区二区| 亚洲国产精品久久久久婷婷884 | 亚洲黄色免费电影| 快射av在线播放一区| av成人免费| 亚洲——在线| 欧美一区二区三区四区视频| 国产欧美日韩精品一区| 欧美好吊妞视频| 久久成年人视频| 欧美福利一区二区| 久久国内精品视频| 韩国成人理伦片免费播放| 久久久久久香蕉网| 国产伦精品一区二区三区视频黑人| 久久亚洲捆绑美女| 亚洲欧洲日产国产网站| 久久午夜精品一区二区| 亚洲国产99| 亚洲免费电影在线| 欧美亚洲综合久久| 欧美黄色小视频| 日韩一二三在线视频播| 欧美顶级少妇做爰| 亚洲一本大道在线| 欧美在线观看www| 国产精品久久9| 日韩午夜在线| 欧美电影在线| 亚洲主播在线播放| 国产日韩成人精品| 欧美在线观看一二区| 久久人人爽人人爽| 亚洲人成绝费网站色www| 久久视频国产精品免费视频在线| 久久精品国产精品亚洲综合| 国产视频久久| 欧美片第一页| 久久国产精品网站| 亚洲一级影院| 亚洲片国产一区一级在线观看| 在线中文字幕不卡| 欧美成年人在线观看| 日韩午夜在线播放| 欧美日韩一级片在线观看| 亚洲资源av| 蜜臀久久久99精品久久久久久 | 欧美性猛片xxxx免费看久爱| 一区二区三区高清在线| 国产日本欧美一区二区三区| 中文一区二区在线观看| 欧美激情视频给我| 亚洲精品欧美激情| 亚洲天堂视频在线观看| 国内激情久久| 国内久久婷婷综合| 亚洲在线观看免费| 久久精品在线免费观看| 久久精品中文字幕一区| 巨乳诱惑日韩免费av| 久久亚洲精选| 亚洲国产一二三| 亚洲激情在线| 欧美中文字幕久久| 久久免费精品视频| 欧美黑人在线播放| 欧美刺激午夜性久久久久久久| 欧美综合国产| 欧美色大人视频| 欧美日韩一区三区| 国外成人在线| 一本久久a久久精品亚洲| 欧美一区二区精品久久911| 欧美亚洲自偷自偷| 欧美成人自拍视频| 国产日韩精品一区| 亚洲第一主播视频| 亚洲日本成人在线观看| 亚洲一区国产精品| 日韩视频一区二区三区在线播放免费观看 | 国产在线观看精品一区二区三区| 在线电影国产精品| 噜噜噜在线观看免费视频日韩| 亚洲福利在线视频| 1024亚洲| 亚洲午夜视频在线| 欧美一区影院| 日韩一二在线观看| 亚洲最快最全在线视频| 亚洲国产精品国自产拍av秋霞| 亚洲福利久久| 国产精品最新自拍| 欧美在线观看日本一区| 免费日韩成人| 美女国产一区| 亚洲图片激情小说| 久久aⅴ国产紧身牛仔裤| 经典三级久久| 亚洲精品小视频| 国产在线日韩| 一区二区精品国产| 黄色成人在线网址| 一区二区三区日韩欧美| 经典三级久久| 亚洲国产精品久久久久久女王| 国内视频一区| 亚洲综合第一| 久久青青草综合| 欧美在线视频观看| 欧美精品一区三区在线观看| 久久av在线看| 国产精品一区一区三区| 亚洲精品一区二区在线| 伊人精品在线| 久久国产主播| 欧美成人免费网| 亚洲第一视频| 欧美激情视频在线播放 | 欧美精品免费观看二区| 老巨人导航500精品|