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

閱讀本文前最好已經讀過 理解程序內存 和 理解C++變量存儲模型 相關的內容, C++對象模型比較經典的書是《深度探索C++對象模型》, 但是書本的知識始終局限在理論上,熟話說“紙上得來終覺淺”,只有我們自已用工具經過驗證,我們才能真正的理解這些知識。下面我們用WinDbg為工具對C++對象模型進行探索。


類對象實例究竟包含哪些東西

我們的例子代碼非常簡單:
#include <iostream>

using namespace  std;

class A
{
public:
    void fun1(){ cout << "fun1"; }
    virtual void fun2() { cout << "fun2"; }
    virtual ~A() {}

    char m_cA;
    int m_nA;
    static int s_nCount;
};

int A::s_nCount = 0;

int main() 
{
    A* p = new A;
    p->fun2();

    system("pause");

    return 0;
}
我們在main函數里 system("pause");的地方設置斷點,然后讓程序運行到這里。

輸入WinDbg命令?? sizeof(*p)讓他打印A對象的大小,輸出如下:
 0:000> ?? sizeof(*p)
unsigned int 0xc
可以看到A的實例對象大小是 0xc = 12 字節

接下來輸入WinDbg命令dt p讓他打印p所指下對象的內存布局, 輸出如下:
0:000> dt p
Local var @ 0x13ff74 Type A*
0x00034600 
   +0x000 __VFN_table : 0x004161d8 
   +0x004 m_cA             : 120 'x'
   +0x008 m_nA             : 0n0
   =0041c3c0 A::s_nCount      : 0n0
可以看到A的對象實例由虛表指針,m_cA, m_nA組成,正好是12字節(內部char作了4字節對齊)。

最后一個靜態變量s_nCount的地址是0041c3c0, 我們可以通過命令!address 0041c3c0查看它所在地址的屬性, 結果如下:
0:000> !address 0041c3c0
Usage:                  Image
Allocation Base:        00400000
Base Address:           0041b000
End Address:            0041f000
Region Size:            00004000
Type:                   01000000    MEM_IMAGE
State:                  00001000    MEM_COMMIT
Protect:                00000004    PAGE_READWRITE
More info:              lmv m ConsoleTest
More info:              !lmi ConsoleTest
More info:              ln 0x41c3c0
可以看到類靜態變量被編譯在consoletest.exe可執行文件的 可讀寫數據節(.data)

結論: C++中類實例對象由虛表指針和成員變量組成(一般最開始的4個字節是虛表指針),而類靜態變量分布在PE文件的.data節中,與類實例對象無關。


虛表位置和內容

根據+0x000 __VFN_table : 0x004161d8  繼續上面的調試,我們看到虛表地址是在0x004161d8, 輸入!address 0x004161d8, 查看虛表地址的屬性:
0:000> !address 0x004161d8
Usage:                  Image
Allocation Base:        00400000
Base Address:           00416000
End Address:            0041b000
Region Size:            00005000
Type:                   01000000    MEM_IMAGE
State:                  00001000    MEM_COMMIT
Protect:                00000002    PAGE_READONLY
More info:              lmv m ConsoleTest
More info:              !lmi ConsoleTest
More info:              ln 0x4161d8
可以看到類虛表被編譯在consoletest.exe可執行文件的 只讀數據節(.rdata)

接下來我們看下虛表中有哪些內容, 輸入dps 0x004161d8 查看虛表所在地址的符號,輸出如下:
0:000> dps 0x004161d8 
004161d8  00401080 ConsoleTest!A::fun2 [f:\test\consoletest\consoletest\consoletest.cpp @ 13]
004161dc  004010a0 ConsoleTest!A::`scalar deleting destructor'
004161e0  326e7566
004161e4  00000000
我們可以看到虛表里正好包含了我們的2個虛函數fun2()和~A().

另外我們也可以多new幾個A的實例試下,我們可以看到他們的虛表地址都是 0x004161d8。

結論: C++中類的虛表內容由虛函數地址組成,虛表分布在PE文件的.rdata節,并且同一類的所有實例共享同一個虛表。


禁止生成虛表會怎樣

我們可以通過__declspec(novtable)來告訴編譯器不要生成虛表,ATL中大量應用這種技術來減小虛表的內存開銷,我們原來的代碼改成
class __declspec(novtable) A
{
public:
    void fun1(){ cout << "fun1"; }
    virtual void fun2() { cout << "fun2"; }
    virtual ~A() {}

    char m_cA;
    int m_nA;
    static int s_nCount;
};
繼續原來的方法調試,我們會看到一運行到p->fun2(), 程序就會Crash, 究竟是什么原因?
用原來的?? sizeof(*p)命令,可以看到對象大小依然是12 字節, 輸入dt p, 輸出:
0:000> dt p
Local var @ 0x13ff74 Type A*
0x00033e58 
   +0x000 __VFN_table : 0x00030328 
   +0x004 m_cA             : 40 '('
   +0x008 m_nA             : 0n0
   =0040dce0 A::s_nCount      : 0n0
從上面可以看到虛表似乎依然存在, 但是再輸入dps 0x00030328 查看虛表內容, 你就會發現現在虛表內容果然已經不存在了:
0:000> dps 0x00030328 
00030328  00030328
0003032c  00030328
00030330  00030330
但是我們的程序還是通過虛表去調用虛函數fun2, 難怪會Crash了。

結論: 通過__declspec(novtable),我們只能禁止編譯器生成虛表,但是不能阻止對象仍包含虛表指針(不能減小對象的大小),也不能阻止程序對虛表的訪問(盡管實際虛表不存在),所以禁止生成虛表只適用于永遠不會實例化的類(基類)


單繼承對象內存模型

下面我們簡單的將上面的代碼改下下,讓B繼承A,并且重寫原來的虛函數fun2:
#include <iostream>

using namespace  std;

class  A
{
public:
    void fun1(){ cout << "fun1"; }
    virtual void fun2() { cout << "fun2"; }
    virtual ~A() {}

    char m_cA;
    int m_nA;
    static int s_nCount;
};

int A::s_nCount = 0;

class B: public A
{
public:
    virtual void fun2() { cout << "fun2 in B"; }
    virtual void fun3() { cout << "fun3 in B"; }

public:
    int m_nB;
};

int main() 
{
    B* p = new B;
    A* p1 = p;

    p1->fun2();

    system("pause");

    return 0;
}
用原來的方法進行調試,查看B對象的內存布局
0:000> dt p
Local var @ 0x13ff74 Type B*
0x00034640 
   +0x000 __VFN_table : 0x004161d8 
   +0x004 m_cA             : 120 'x'
   +0x008 m_nA             : 0n0
   =0041c3e0 A::s_nCount      : 0n0
   +0x00c m_nB             : 0n0
可以看到B對象的大小是原來A對象的大小加4(m_nB), 總共是16字節,查看B的虛表內容如下:
0:000> dps 0x004161d8 
004161d8  00401080 ConsoleTest!B::fun2 [f:\test\consoletest\consoletest\consoletest.cpp @ 26]
004161dc  004010c0 ConsoleTest!B::`scalar deleting destructor'
004161e0  004010a0 ConsoleTest!B::fun3 [f:\test\consoletest\consoletest\consoletest.cpp @ 27]
004161e4  326e7566
可以看到虛表中保存的都是B的虛函數地址: fun2(), ~B(), fun3()

結論: 單繼承時父類和子類共用同一虛表指針,而子類的數據被添加在父類數據之后,父類和子類的對象指針在相互轉化時值不變。


多繼承對象內存模型

我們把上面的代碼改成多繼承的方式, class A, class B, 然后C繼承A和B:
#include <iostream>
using namespace  std;
class  A
{
public:
virtual void fun()  {cout << "fun in A";}
virtual void funA() {cout << "funA";}
virtual ~A() {}
char m_cA;
int m_nA;
static int s_nCount;
};
int A::s_nCount = 0;
class B
{
public:
virtual void fun() {cout << "fun in B";}
virtual void funB() {cout << "funB";}
int m_nB;
};
class C: public A, public B 
{
public:
virtual void fun() {cout << "fun in C";};
virtual void funC(){cout << "funC";}
int m_nC;
};
int main() 
{
C* p = new C;
B* p1 = p;
p->fun();
system("pause");
return 0;
}
依舊用原來的方式調試,查看C的內存布局
0:000> dt p
Local var @ 0x13ff74 Type C*
0x00034600 
   +0x000 __VFN_table : 0x004161f4 
   +0x004 m_cA             : 120 'x'
   +0x008 m_nA             : 0n0
   =0041c4a0 A::s_nCount      : 0n0
   +0x00c __VFN_table : 0x004161e8 
   +0x010 m_nB             : 0n0
   +0x014 m_nC             : 0n0
可以看到C對象由0x18 = 24字節組成,可以看到數據依次是虛表指針,A的數據,虛表指針, B的數據, C的數據。

查看第一個虛表內容:
0:000> dps 0x004161f4 
004161f4  004010f0 ConsoleTest!C::fun [f:\test\consoletest\consoletest\consoletest.cpp @ 33]
004161f8  004010b0 ConsoleTest!A::funA [f:\test\consoletest\consoletest\consoletest.cpp @ 16]
004161fc  00401130 ConsoleTest!C::`scalar deleting destructor'
00416200  00401110 ConsoleTest!C::funC [f:\test\consoletest\consoletest\consoletest.cpp @ 34]
可以看到前面虛表的前面3個虛函數符合A的虛表要求(第一個A::fun讓C::fun取代了,第三個A的析構函數~A讓~C取代了),最后加上了C的新增虛函數funC, 所以該虛表同時符合A和C的要求,也就是說A和C共用同一個虛表指針。

再看第二個虛表內容:
0:000> dps 0x004161e8 
004161e8  00402850 ConsoleTest![thunk]:C::fun`adjustor{12}'
004161ec  004010d0 ConsoleTest!B::funB [f:\test\consoletest\consoletest\consoletest.cpp @ 27]
004161f0  004187a0 ConsoleTest!C::`RTTI Complete Object Locator'
可以看到第二個虛表符合B的虛表要求,并且把B的虛函數fun用C的改寫了,所以它是給B用的。 

我們再看基類對象B的布局情況:
0:000> dt p1
Local var @ 0x13ff70 Type B*
0x0003460c 
   +0x000 __VFN_table : 0x004161e8 
   +0x004 m_nB             : 0n0
我們可以看到p1指針本身在堆棧上的地址是0x13ff70,而p1所指向對象的地址是0x003e460c ,所以將C指針轉成B指針后,B的地址和C的地址之差是0x003e460c0x00034600  = 0xc = 12字節, 也就是說B的指針p1指向我們上面的第二個虛表指針。

另外我們上面要特別留意第二個虛表的第一個函數:004161e8  00402850 ConsoleTest![thunk]:C::fun`adjustor{12}'
我們發現這個函數不是我們真正的class C的fun函數:004161f4  004010f0 ConsoleTest!C::fun [f:\test\consoletest\consoletest\consoletest.cpp @ 33]
該函數地址是00402850, 我們可以反匯編看下:
0:000> u 00402850
ConsoleTest![thunk]:C::fun`adjustor{12}':
00402850 83e90c          sub     ecx,0Ch
00402853 e998e8ffff      jmp     ConsoleTest!C::fun (004010f0)
00402858 cc              int     3
00402859 cc              int     3
0040285a cc              int     3
0040285b cc              int     3
0040285c cc              int     3
0040285d cc              int     3
可以看到這個函數是編譯器生成的一個代理函數,它內部實現只是把我們B的this指針(ecx)加上12個字節的偏移后,然后再去調用我們真正的C的fun函數。
為什么會這樣呢? 因為class C的fun 內部在實現時假設的this指針都是它本身實例的起始地址,但是B指針并不符合這個要求,所以B的指針需要調整后才能去調用真正C的方法。

結論: 多重繼承時派生類和第一個基類公用一個虛表指針,他們的對象指針相互轉化時值不變;而其他基類(非第一個)和派生類的對象指針在相互轉化時有一定的偏移,他們內部虛表保存的函數指針并不一定是最終的實現的虛函數(可能是類似上面的一個代理函數)。



如何用虛表實現多態?

有了上面這些分析,這個咱們就不證明了,直接下結論吧。

結論: C++通過虛表來實現多態,派生類的虛表和基類的虛表根據索引依次保存相同的函數類型指針,但是這些函數指針最終指向他們各自最終的實現函數,調用虛函數時,我們只是根據函數所在虛表的索引來調用,所以他們可以在派生類中有各自不同的實現。 



虛擬繼承

恩,有了前面的基礎,這個就當思考題吧...


總之,拿著一把刀,庖丁解牛般的剖析語言背后的實現細節,看起來不是那么實用,但是它能讓你對語言的理解更深刻。實際上ATL中大量應用上面的技術,如果沒有對C++ 對象模型有比較深刻的理解,是很難深入下去的。
posted on 2012-09-21 23:02 Richard Wei 閱讀(4190) 評論(2)  編輯 收藏 引用 所屬分類: C++

FeedBack:
# re: 探索C++對象模型
2015-08-18 13:32 | lvshiling@qq.com
可以看到這個函數是編譯器生成的一個代理函數,它內部實現只是把我們B的this指針(ecx)加上12個字節的偏移后,然后再去調用我們真正的C的fun函數。

應修改為

可以看到這個函數是編譯器生成的一個代理函數,它內部實現只是把我們B的this指針(ecx)減去12個字節的偏移后,然后再去調用我們真正的C的fun函數。  回復  更多評論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            午夜激情综合网| 欧美日韩中文精品| 女人天堂亚洲aⅴ在线观看| 欧美一进一出视频| 欧美中文字幕视频| 先锋影音久久久| 久久大香伊蕉在人线观看热2| 午夜精品一区二区三区在线视| 亚洲欧美中文另类| 久久久精品999| 欧美成人亚洲成人| 亚洲精品资源美女情侣酒店| 在线视频亚洲欧美| 香蕉久久国产| 欧美大片一区| 欧美无乱码久久久免费午夜一区| 国产精品一国产精品k频道56| 国产中文一区| 夜夜嗨av一区二区三区免费区| 亚洲欧美一区二区三区极速播放| 午夜国产不卡在线观看视频| 欧美日韩国产色综合一二三四| 欧美日韩国产成人在线免费| 国产精品免费久久久久久| 国产精品久久久久久影视| 国产午夜精品一区二区三区欧美| 悠悠资源网久久精品| 日韩香蕉视频| 久久精品99无色码中文字幕| 亚洲国产精品久久久| 99天天综合性| 久久人人97超碰精品888| 欧美三级小说| 99国产欧美久久久精品| 久久精品最新地址| 一区二区日韩伦理片| 麻豆成人在线播放| 国产日韩欧美三区| 一本久久a久久精品亚洲| 久久久久久综合| 亚洲美女诱惑| 欧美夫妇交换俱乐部在线观看| 国产一本一道久久香蕉| 99天天综合性| 欧美国产日韩一二三区| 亚洲一区二区免费看| 男女av一区三区二区色多| 国模套图日韩精品一区二区| 在线综合亚洲| 亚洲片国产一区一级在线观看| 久久久久久成人| 国精品一区二区三区| 欧美一区二区三区日韩| 亚洲性感美女99在线| 欧美视频在线一区二区三区| 99精品欧美一区二区三区| 亚洲国产高清视频| 欧美www视频| 亚洲国产成人精品视频| 美女图片一区二区| 午夜精品在线看| 国产精品实拍| 欧美一激情一区二区三区| 一区二区免费在线视频| 欧美偷拍另类| 亚洲综合第一页| 欧美国产日韩精品| 久久中文在线| 亚洲国产午夜| 亚洲黄色成人| 欧美区视频在线观看| 艳女tv在线观看国产一区| 亚洲美女少妇无套啪啪呻吟| 欧美金8天国| 亚洲图中文字幕| 亚洲小说欧美另类婷婷| 国产精品日韩电影| 亚洲欧美日韩中文视频| 亚洲欧美色婷婷| 国内精品伊人久久久久av一坑| 国产欧美日韩中文字幕在线| 午夜在线观看欧美| 国产精品99久久久久久久久| 国产美女精品人人做人人爽| 久久一区中文字幕| 欧美精品一卡二卡| 亚洲欧美日韩视频一区| 久久av资源网站| 91久久久精品| 亚洲一区日韩在线| 狠狠色狠狠色综合| 亚洲激情一区二区| 国产乱码精品| 欧美专区第一页| 欧美凹凸一区二区三区视频| 亚洲免费一区二区| 久久久国产成人精品| 亚洲激情一区二区| 亚洲午夜在线视频| 91久久中文| 西瓜成人精品人成网站| 亚洲精品网站在线播放gif| 亚洲午夜精品久久久久久app| 黑丝一区二区| 亚洲午夜精品久久久久久浪潮| 国产综合视频| 亚洲天堂激情| 亚洲美女淫视频| 久久福利一区| 午夜精品久久久久影视| 欧美激情第一页xxx| 久久狠狠一本精品综合网| 欧美激情区在线播放| 蜜桃久久精品乱码一区二区| 国产日韩欧美制服另类| 中文精品视频一区二区在线观看| 亚洲国产精品va在看黑人| 亚洲欧美日韩综合一区| 亚洲图色在线| 欧美日韩另类一区| 亚洲国产精品日韩| 在线看欧美视频| 久久精品论坛| 久久久噜噜噜| 国产欧美视频在线观看| 亚洲最新在线| 在线视频欧美一区| 欧美韩国日本综合| 欧美成人69av| 尤物网精品视频| 久久精品国产一区二区三区免费看 | 国产欧美日韩在线视频| 亚洲图片欧洲图片av| 99re视频这里只有精品| 欧美黄色免费网站| 亚洲东热激情| 亚洲国产一区二区三区在线播| 午夜精品福利一区二区三区av| 亚洲免费在线播放| 欧美视频不卡中文| 一本久道久久久| 亚洲欧美日韩在线| 国产美女扒开尿口久久久| 亚洲欧美色婷婷| 在线精品视频一区二区三四| 久久蜜桃精品| 亚洲第一黄色网| 亚洲精品综合精品自拍| 欧美jizzhd精品欧美喷水 | 亚洲免费视频一区二区| 午夜精品免费视频| 国产欧美日韩三级| 久久精彩视频| 欧美电影免费观看高清| 亚洲精品免费在线观看| 欧美啪啪成人vr| 亚洲一区国产| 久久综合色一综合色88| 亚洲人成网站色ww在线| 欧美日韩免费看| 亚洲欧美日韩国产成人| 蜜臀99久久精品久久久久久软件| 日韩一二三区视频| 国产精品久久久久久久久免费| 欧美怡红院视频| 91久久国产综合久久蜜月精品 | 欧美日韩视频第一区| 亚洲一区二区在线| 欧美成人精品一区二区三区| 一区二区三区久久精品| 国产欧美日韩精品丝袜高跟鞋| 另类激情亚洲| 亚洲一区二区三区久久| 欧美韩日一区| 亚洲欧美国产77777| 影音先锋日韩精品| 国产精品国产三级国产aⅴ9色| 久久久久久97三级| 亚洲性视频h| 亚洲第一在线视频| 欧美中文在线视频| 在线视频欧美日韩| 1000精品久久久久久久久| 欧美视频免费在线观看| 老鸭窝91久久精品色噜噜导演| 亚洲欧美日韩另类| 最新日韩在线| 另类成人小视频在线| 香蕉成人久久| 一二三区精品福利视频| 亚洲国产精品成人精品| 国产亚洲欧洲一区高清在线观看| 欧美精品日韩精品| 久久免费视频在线观看| 午夜免费电影一区在线观看| 夜色激情一区二区| 欧美激情一区二区三区在线视频观看 | ●精品国产综合乱码久久久久| 国产日韩欧美视频| 国产精品免费观看在线|