程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++虛擬多重繼承對象模型討論

C++虛擬多重繼承對象模型討論

編輯:C++入門知識

C++虛擬多重繼承對象模型討論

作者:magictong

調試環境:Windows7VS2005

概述

記得剛開始寫C++程序時,那還是大學時光,感覺這玩意比C強大多了,怎麼就實現了多態,RTTI這些牛逼的玩意呢?當時沒有深究,後來零零散散看過一些介紹的文章,也看了一些相關的書籍,總覺得說得不甚清楚。而這些問題的本質還是在於C++對象的內存模型問題,數據結構決定了你的算法嘛,在這裡也是基本適用的。網上有很多講C++對象模型的文章,但是大部分都是涉及基本繼承,多重繼承等等,而對於虛擬多重繼承的情況則涉及不多,這篇文章則主要講述這種情況下C++的對象模型情況,希望能夠起到拋磚引玉的作用。

本文分三個步驟由淺入深的討論這個問題。

一、先看一個最簡單的例子

KVBase有一個虛函數Run(),KA從KVBase虛擬繼承並且覆蓋Run()函數。

源代碼

#include"stdafx.h"

#include

usingnamespacestd;

//先看一個簡單的例子

classKVBase

{

public:

KVBase():m_nBase(1){}

virtualvoidRun()

{

cout <<"KVBase::Run()is called." <

}

private:

intm_nBase;

};

classKA :virtualpublic KVBase

{

public:

KA():m_nb(2){}

virtualvoidRun()

{

cout <<"KA::Run()is called." <

}

private:

intm_nb;

};

int_tmain(intargc,_TCHAR*argv[])

{

KAa;

KVBase*pBase = &a;

cout<<"The Base Address:"<

cout<<"//=============>ObjectInfomation: " <

cout<

cout<

cout<

cout<

cout<

pBase->Run();

return0;

}

輸出:

\

參考一下調試的結果(第一個圖是視圖,直觀反映成員情況但不是具體的內存對象模型,第二張圖才是內存中的真正數據排布)。

\

a對象內存分布情況:

\

0x00403238是a對象的虛基類指針,注意不是虛表指針,我開始也以為是虛表指針,但是根據後面的分析,在這種情況下,a對象因為沒有自己的獨特虛函數,實際上它不需要專門的虛指針了,共用虛基類的就可以了(至少我認為它是這麼設計的^_^)。它裡面的兩個成員第一個成員是目前只發現有兩個可能的值0和-4,如果當前類沒有獨特的虛函數,則值是0,否則是-4,我把它簡單稱之為一個標志位,第二個成員則是一個當前位置到虛基類地址的偏移量,單位是字節(0x00ff30+0x0c=0x00ff3c)。

\

0x00403234則是虛基類的虛表指針,0x004015d0實際就是KA::Run的地址。

\

這種情況下,虛表結構圖大概是這樣的(虛基類在最後面):

\

二、繼承類有自己特有的虛函數

稍微變動一下,給KA加一個自己的獨特的虛函數RunKA(),分析方法同上,此時你會發現內存布局發生了一些小變化,最大的變化是KA類有了自己的虛表。

classKA :virtualpublic KVBase

{

public:

KA():m_nb(2){}

virtualvoidRun()

{

cout <<"KA::Run()is called." <

}

virtualvoidRunKA()

{

cout <<"KA::RunKA()is called." <

}

private:

intm_nb;

};

輸出:

\

其它部分不變,再次調試發現,內存是這樣了:

\

視圖:

\

0x00403258是KA類的虛表指針,裡面的0x00401120正是KA::RunKA()的地址。

\

0x00403264是虛基類指針,存放著-4(0xfffffffc的補碼,上面討論過這種情況下表示當前類有自己的特有虛函數)和0x0c(偏移)。

\

0x403260是虛基類的虛表指針,裡面存放這KA::Run()地址。

\

因此,這種情況下,虛表圖大概是這樣的(虛基類依然在最後面):

\

三、鑽石型繼承

好吧,分析一個復雜的鑽石型繼承情況之後收工,為了說明的完整性,我貼一下全部的代碼。

#include"stdafx.h"

#include

usingnamespacestd;

classKVBase

{

public:

KVBase():m_nBase(1){}

virtualvoidRun()

{

cout <<"KVBase::Run()is called." <

}

private:

intm_nBase;

};

classKA :virtualpublic KVBase

{

public:

KA():m_na(2){}

virtualvoidRun()

{

cout <<"KA::Run()is called." <

}

virtualvoidRunKA()

{

cout <<"KA::RunKA()is called." <

}

private:

intm_na;

};

classKB :virtualpublic KVBase

{

public:

KB():m_nb(3),m_nb2(0x1022){}

virtualvoidRun()

{

cout <<"KB::Run()is called." <

}

virtualvoidRunKB()

{

cout <<"KB::RunKB()is called." <

}

virtualvoidFuncKB()

{

cout <<"KB::FuncKB()is called." <

}

private:

intm_nb;

intm_nb2;

};

classDChild :publicKA,public KB

{

public:

DChild():m_ndChild(4){}

virtualvoidRun()

{

cout <<"DChild::Run()is called." <

}

virtualvoidRunKA()

{

cout <<"DChild::RunKA()is called." <

}

virtualvoidRunKB()

{

cout <<"DChild::RunKB()is called." <

}

virtualvoidFuncDChild()

{

cout <<"DChild::FuncDChild()is called." <

}

private:

intm_ndChild;

};

int_tmain(intargc,_TCHAR*argv[])

{

DChilda;

KVBase*pBase = &a;

cout<<"The Base Address:"<

cout<<"//=============>ObjectInfomation: " <

cout<

cout<

cout<

cout<

cout<

cout<

cout<

cout<

cout<

cout<

cout<

pBase->Run();

return0;

}

運行一下:

\

現在開始變得有些有趣了,先看一下視圖,大概了解一下整個布局。

\

然後一個個的看打印出來的那些地址都代表著什麼。

0x00403384是KA的虛表指針,其中0x00401330是DChild::RunKA()地址,0x00401390是DChild::FuncDChild()地址,很明顯,DChild類的虛函數是放在這個虛表裡面的。

\

0x004033a0是KA的虛基類指針,標志位依然是-4,偏移則是0x20,算一下,0x0012FF1C+0x20果然是0x0012FF3C。

\

跟KB類相關的兩個地址,0x00403390和0x004033a8的地址內容跟KA類是相似的(不過虛表就沒有DChild的虛函數指針了),聰明的小伙伴可以自己去看下。

至於0x0040339c則依然是虛基類虛表指針,現在虛表裡面是函數DChild::Run()地址。

\

直接上圖,這種情況下,虛表結構圖大概是這樣的:

\

總結

其實C++的對象內存模型在不同的編譯器下面是有差異的,有興趣的同學可以在GCC之類的環境下測試一下,但是整體的設計思想其實都是大同小異的,我們也許沒有必要把所有的細節都弄得極其清楚,但是學習這個思想才是最根本的,思考一下前人為什麼要這麼設計?!這麼設計的好處是什麼?!他們想解決什麼問題?!

參考文獻

[1] 虛函數表解析http://blog.csdn.net/haoel/article/details/1948051

[2] C++對象的內存布局http://blog.csdn.net/haoel/article/details/3081328

[3] 《深度探索C++對象模型》侯捷


樣例代碼下載

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved