c++單繼承、多繼承、菱形繼承的記憶體佈局(虛擬函式表結構)
阿新 • • 發佈:2019-01-06
單繼承:只有一個基類和一個派生類
class Base
{
public:
virtual void fun1()
{
cout << "Base::func1()" << endl;
}
virtual void fun2()
{
cout << "Base::func2()" << endl;
}
private:
int b;
};
class Derive :public Base
{
public:
virtual void fun1() //重寫基類虛擬函式,實現多型
{
cout << "Derive::func1()" << endl;
}
virtual void fun3()
{
cout << "Derive::func3()" << endl;
}
void fun4()
{
cout << "Derive::func4()" << endl;
}
private:
int d;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
1. 虛表就是存放虛擬函式的表。
2. 主函式中分別定義一個基類物件和一個派生類物件,通過除錯視窗可以看到所謂的虛表,如下圖(整型b和d未初始化):
>也許你會有疑問:除錯視窗中派生類虛表為什麼看不到Derive中的fun3()函式,這是編譯器的問題,我所用的是vs2013,在除錯的時候確實不見fun3()函式,所以有時編譯器的除錯視窗顯示的也不能完全相信,那有什麼辦法證明fun3()函式也在派生類虛表裡呢?通過列印虛表!
程式碼如下:
typedef void (*FUNC)(); //重定義函式指標,指向函式的指標
void PrintVTable(int* vTable) //列印虛擬函式表
{
if (vTable == NULL)
{
return;
}
cout << "虛擬函式表地址:" << vTable << endl;
int i = 0;
for (; vTable[i] != 0; ++i)
{
printf(" 第%d個虛擬函式地址 :0X%x,->", i, vTable[i]);
FUNC f = (FUNC)vTable[i];
f(); //訪問虛擬函式
}
cout << endl;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
void Test1()
{
Base b;
Derive d;
int* tmp = (int*)(*(int*)&b); //取到虛擬函式的地址
PrintVTable(tmp);
int* tmp1 = (int*)(*(int*)&d);
PrintVTable(tmp1);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
解析:int* tmp = (int*)(*(int*)&b);
如下圖:
列印虛表:
>注意:
不知你是否注意到派生類中還有一個函式:void fun4();
虛擬函式是為了實現動態多型,是當程式執行到該函式時才會去虛表裡找這個函式;而函式的過載實現的是靜態多型, 是在程式編譯時就能找到該函式地址,而函式:void fun4();不是虛擬函式,自然不會在虛擬函式表裡。
class Base1 //基類
{
public:
virtual void fun1()
{
cout << "Base1::fun1" << endl;
}
virtual void fun2()
{
cout << "Base1::fun2" << endl;
}
private:
int b1;
};
class Base2 //基類
{
public:
virtual void fun1()
{
cout << "Base2::fun1" << endl;
}
virtual void fun2()
{
cout << "Base2::fun2" << endl;
}
private:
int b2;
};
class Derive : public Base1, public Base2 //派生類
{
public:
virtual void fun1()
{
cout << "Derive::fun1" << endl;
}
virtual void fun3()
{
cout << "Derive::fun3" << endl;
}
private:
int d1;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
除錯看結果:
同樣以上面單繼承列印虛表函式來列印多繼承虛表:void PrintVTable(int*
vTable); //列印虛擬函式表
void Test1()
{
Derive d1;
int* VTable = (int*)(*(int*)&d1);
PrintVTable(VTable);
VTable = (int*)(*((int*)&d1 + sizeof (Base1)/4));
PrintVTable(VTable);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
解析:VTable = (int*)(*((int*)&d1 + sizeof (Base1)/4));
列印多繼承虛表如下圖:
先了解什麼是菱形繼承?
下列程式碼是菱形繼承體系:
class Base //Derive的間接基類
{
public:
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func2()
{
cout << "Base::func2()" << endl;
}
private:
int b;
};
class Base1 :public Base //Derive的直接基類
{
public:
virtual void func1() //重寫Base的func1()
{
cout << "Base1::func1()" << endl;
}
virtual void func3()
{
cout << "Base1::func3()" << endl;
}
private:
int b1;
};
class Base2 :public Base //Derive的直接基類
{
public:
virtual void func1() //重寫Base的func1()
{
cout << "Base2::func2()" << endl;
}
virtual void func4()
{
cout << "Base2::func4()" << endl;
}
private:
int b2;
};
class Derive :public Base1, public Base2
{
public:
virtual void func1() //重寫Base1的func1()
{
cout << "Derive::func1()" << endl;
}
virtual void func5()
{
cout << "Derive::func5()" << endl;
}
private:
int d;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
菱形繼承其實是一個單繼承與多繼承的結合。
下面跟蹤Derive物件d的記憶體佈局:
進一步解析如下圖:
同樣以上面單繼承列印虛表函式來列印多繼承虛表:void PrintVTable(int*
vTable); //列印虛擬函式表
>1. 先來看下面兩個方案:
> 主要解析第一種方案:
vs2003下虛繼承的VBPTR及VBTBL:
在類中增加一個指標(VBPTR)指向一個VBTBL,這個VBTBL的第一項記載的是從VBPTR 與本類的偏移地址,如果本類有虛擬函式,那麼第一項是FF FF FF FC(也就是-4),如果沒有則是零,第二項起是VBPTR與本類的虛基類的偏移值。
下面這段程式碼與上面菱形繼承(非虛繼承)類似:
class Base
{
public:
virtual void fun1()
{
cout << "Base::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base::fun2()" << endl;
}
private:
int b;
};
class Base1 :virtual public Base 虛繼承
{
public:
virtual void fun1() //重寫Base的func1()
{
cout << "Base1::fun1()" << endl;
}
virtual void fun3()
{
cout << "Base1::fun3()" << endl;
}
private:
int b1;
};
class Base2 :virtual public Base //虛繼承
{
public:
virtual void fun1() //重寫Base的func1()
{
cout << "Base2::fun1()" << endl;
}
virtual void fun4()
{
cout << "Base2::fun4()" << endl;
}
private:
int b2;
};
class Derive :public Base1, public Base2
{
public:
virtual void fun1() //重寫Base1的func1()
{
cout << "Derive::fun1()" << endl;
}
virtual void fun5()
{
cout << "Derive::fun5()" << endl;
}
private:
int d;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
1. 詳細地分析一下vs2003下虛繼承的VBPTR及VBTBL:
- 以Base1 b1;為例子,詳細分析記憶體佈局如下(Base2和Base1的記憶體佈局相似):
sizeof(Base1) = 20;(下圖中黑色區域中所有變數所佔的大小)
當在主函式中定義兩個物件Base1 b1和Base2 b2時,還可通過除錯進一步探索其記憶體佈局如下:
最後,我們再來探索一下 Derive d 的記憶體佈局,首先我們先通過除錯視窗來跟蹤如下:
通過上面除錯視窗可能沒辦法瞭解全部,那麼請看下圖所示:
sizeof(Derive) = 36;(下圖黑色區域所有變數的大小)
我們怎麼驗證菱形繼承(虛繼承)的記憶體佈局就是這樣的呢?我們可以通過列印虛表!!
typedef void(*FUNC)();
void PrintVPTR(int* VPTR) //列印虛表(虛擬函式)
{
cout << "虛擬函式表地址:" << VPTR << endl;
for (int i = 0; VPTR[i] != 0; ++i)
{
printf("第%d個虛擬函式地址:0X%x->", i, VPTR[i]);
FUNC f = (FUNC)VPTR[i];
f();
}
cout << endl;
}
void PrintVBPTR(int* VBPTR) //列印偏移地址與值
{
cout << "虛擬函式表地址:" << VBPTR << endl;
int i = 0;
printf("與本類的偏移地址:0X%x\n", VBPTR[i]);
for (i = 1; VBPTR[i] != 0; i++)
{
cout << VBPTR[i] << " " << endl;
}
cout << endl;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
主函式中的呼叫如下:
void Test1()
{
Base b;
Base1 b1;
Base2 b2;
Derive d;
cout << "sizeof_Base = " << sizeof(Base) << endl;
int* BvTable = (int*)(*((int*)&b));
PrintVPTR(BvTable);
cout << "-------------------------------" << endl;
cout << "sizeof_Base1 = " << sizeof(Base1) << endl;
int* BVPTR1 = (int*)(*((int*)&b1)); //存放自己的虛擬函式(虛表)
PrintVPTR(BVPTR1);
int* VBPTR1 = (int*)(*((int*)&b1 + 1));//訪問偏移地址以及偏移量
PrintVBPTR(VBPTR1);
int* VPTR1 = (int*)(*((int*)&b1 + (*(VBPTR1 + 1)) / 4 + 1)); //在Base1中訪問Base虛表
PrintVPTR(VPTR1);
cout << "-------------------------------" << endl;
cout << "sizeof_Base2 = " << sizeof(Base2) << endl;
int* BVPTR2 = (int*)(*((int*)&b2)); //存放自己的虛擬函式(虛表)
PrintVPTR(BVPTR2);
int* VBPTR2 = (int*)(*((int*)&b2 + 1));//訪問偏移地址以及偏移量
PrintVBPTR(VBPTR2);
int* VPTR2 = (int*)(*((int*)&b2 + (*(VBPTR2 + 1)) / 4 + 1));//在Base2中訪問Base虛表
PrintVPTR(VPTR2);
cout << "-------------------------------" << endl;
cout << "sizeof_Derive = " << sizeof(Derive) << endl;
int* dVPTR1 = (int*)(*((int*)&d)); //存放自己的虛擬函式(虛表)
PrintVPTR(dVPTR1);
int* dVBPTR3 = (int*)(*((int*)&d + 1));//訪問偏移地址以及偏移量
PrintVBPTR(dVBPTR3);
int* dVPTR2 = (int*)(*((int*)&d + 3)); //在Derive中訪問Base2虛表
PrintVPTR(dVPTR2);
int* dVBPTR = (int*)(*((int*)&d + 4));//訪問偏移地址以及偏移量
PrintVBPTR(dVBPTR);
int* VPTR = (int*)(*((int*)&d + (*(dVBPTR3 + 1)) / 4 + 1)); //在Derive中訪問Base虛表
PrintVPTR(VPTR);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
總結:
本文轉自http://blog.csdn.net/sulijuan66/article/details/48897867
- 虛基類例項地址 = 派生類虛擬函式指標+派生類虛擬函式指標到虛基類例項地址的偏移量
- 可以通過虛擬繼承消除二義性,但是虛擬繼承的開銷是增加虛擬函式指標。