原創做品,轉載請標明:http://blog.csdn.net/xiejingfa/article/details/48028491ios
準備工做工具
一、VS2012使用命令行選項查看對象的內存佈局佈局
微軟的Visual Studio提供給用戶顯示C++對象在內存中的佈局的選項:/d1reportSingleClassLayout。使用方法很簡單,直接在[工具(T)]選項下找到「Visual Studio命令提示(C)」後點擊便可。切換到cpp文件所在目錄下輸入以下的命令便可.net
c1 [filename].cpp /d1reportSingleClassLayout[className]命令行
其中[filename].cpp就是咱們想要查看的class所在的cpp文件,[className]指咱們想要查看的class的類名。(下面舉例說明...)指針
二、查看普通多繼承子類的內存佈局orm
既然咱們今天講的是虛基類和虛繼承,咱們就先用上面介紹的命令提示工具查看一下普通多繼承子類的內存佈局,能夠跟後文虛繼承子類的內存佈局狀況加以比較。對象
咱們新建一個名叫NormalInheritance的cpp文件,輸入一下內容。blog
/**
普通繼承(沒有使用虛基類)
*/
// 基類A
class A
{
public:
int dataA;
};
class B : public A
{
public:
int dataB;
};
class C : public A
{
public:
int dataC;
};
class D : public B, public C
{
public:
int dataD;
};繼承
上面是一個簡單的多繼承例子,咱們啓動Visual Studio命令提示功能,切換到NormalInheritance.cpp文件所在目錄,輸入一下命令:
c1 NormalInheritance.cpp /d1reportSingleClassLayoutD
咱們能夠看到class D的內存佈局以下:
從類D的內存佈局能夠看到A派生出B和C,B和C中分別包含A的成員。再由B和C派生出D,此時D包含了B和C的成員。這樣D中就總共出現了2個A成員。你們注意到左邊的幾個數字,這幾個數字代表了D中各成員在D中排列的起始地址,D中的五個成員變量(B::dataA、dataB、C::dataA、dataC、dataD)各佔用4個字節,sizeof(D) = 20。
爲了跟後文加以比較,咱們再來看看B和C的內存佈局:
虛繼承的內存分佈狀況
上面咱們看到了普通多繼承子類的內存分佈狀況,下面咱們進入主題,來看看典型的菱形虛繼承子類的內存分佈狀況。
咱們新建一個名叫VirtualInheritance的cpp文件,輸入一下內容:
/**
虛繼承(虛基類)
*/
#include <iostream>
// 基類A
class A
{
public:
int dataA;
};
class B : virtual public A
{
public:
int dataB;
};
class C : virtual public A
{
public:
int dataC;
};
class D : public B, public C
{
public:
int dataD;
};
VirtualInheritance.cpp和NormalInheritance.cpp的不一樣點在與C和C繼承A時使用了virtual關鍵字,也就是虛繼承。一樣,咱們看看B、C、D類的內存佈局狀況:
咱們能夠看到,菱形繼承體系中的子類在內存佈局上和普通多繼承體系中的子類類有很大的不同。對於類B和C,sizeof的值變成了12,除了包含類A的成員變量dataA外還多了一個指針vbptr,類D除了繼承B、C各自的成員變量dataB、dataA和本身的成員變量外,還有兩個分別屬於B、C的指針。
那麼類D對象的內存佈局就變成以下的樣子:
vbptr:繼承自父類B中的指針
int dataB:繼承自父類B的成員變量
vbptr:繼承自父類C的指針
int dataC:繼承自父類C的成員變量
int dataD:D本身的成員變量
int A:繼承自父類A的成員變量
顯然,虛繼承之因此可以實如今多重派生子類中只保存一份共有基類的拷貝,關鍵在於vbptr指針。那vbptr到底指的是什麼?又是如何實現虛繼承的呢?其實上面的類D內存佈局圖中已經給出答案:
實際上,vbptr指的是虛基類表指針(virtual base table pointer),該指針指向了一個虛表(virtual table),虛表中記錄了vbptr與本類的偏移地址;第二項是vbptr到共有基類元素之間的偏移量。在這個例子中,類B中的vbptr指向了虛表D::$vbtable@B@,虛表代表公共基類A的成員變量dataA距離類B開始處的位移爲20,這樣就找到了成員變量dataA,而虛繼承也不用像普通多繼承那樣維持着公共基類的兩份一樣的拷貝,節省了存儲空間。
爲了進一步肯定上面的想法是否正確,咱們能夠寫一個簡單的程序加以驗證:
int main()
{
D* d = new D;
d->dataA = 10;
d->dataB = 100;
d->dataC = 1000;
d->dataD = 10000;
B* b = d; // 轉化爲基類B
C* c = d; // 轉化爲基類C
A* fromB = (B*) d;
A* fromC = (C*) d;
std::cout << "d address : " << d << std::endl;
std::cout << "b address : " << b << std::endl;
std::cout << "c address : " << c << std::endl;
std::cout << "fromB address: " << fromB << std::endl;
std::cout << "fromC address: " << fromC << std::endl;
std::cout << std::endl;
std::cout << "vbptr address: " << (int*)d << std::endl;
std::cout << " [0] => " << *(int*)(*(int*)d) << std::endl;
std::cout << " [1] => " << *(((int*)(*(int*)d)) + 1)<< std::endl; // 偏移量20
std::cout << "dataB value : " << *((int*)d + 1) << std::endl;
std::cout << "vbptr address: " << ((int*)d + 2) << std::endl;
std::cout << " [0] => " << *(int*)(*((int*)d + 2)) << std::endl;
std::cout << " [1] => " << *((int*)(*((int*)d + 2)) + 1) << std::endl; // 偏移量12
std::cout << "dataC value : " << *((int*)d + 3) << std::endl;
std::cout << "dataD value : " << *((int*)d + 4) << std::endl;
std::cout << "dataA value : " << *((int*)d + 5) << std::endl;
}
獲得結果爲: