C++虛繼承 虛函數對C++對象內存模型造成的影響(類/對象大小)
首先重新回顧一下關於類/對象大小的計算原則:
類大小計算遵循結構體對齊原則
第一個數據成員放在offset為0的位置
其它成員對齊至min(sizeof(member),#pragma pack(n)所指定的值)的整數倍。
整個結構體也要對齊,結構體總大小對齊至各個min中最大值的整數倍。
win32 可選的有1, 2, 4, 8, 16
linux 32 可選的有1, 2, 4
類的大小與數據成員有關與成員函數無關
類的大小與靜態數據成員無關
虛繼承對類的大小的影響
虛函數對類的大小的影響
下面通過實例來展示虛繼承和虛函數對類大小造成的影響。
測試環境為:Win32 + Vs2008
一、只出現虛繼承的情況
C++ Code
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
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
using namespace std;
class BB
{
public :
int bb_ ;
};
class B1 : virtual public BB
{
public :
int b1_ ;
};
class B2 : virtual public BB
{
public :
int b2_ ;
};
class DD : public B1, public B2
{
public :
int dd_ ;
};
int main (void)
{
cout<<sizeof (BB)<< endl;
cout<<sizeof (B1)<< endl;
cout<<sizeof (DD)<< endl;
B1 b1 ;
int** p ;
cout<<&b1 <<endl;
cout<<&b1 .bb_<< endl;
cout<<&b1 .b1_<< endl;
p = (int **)&b1;
cout<<p [0][0]<<endl;
cout<<p [0][1]<<endl;
DD dd ;
cout<<&dd <<endl;
cout<<&dd .bb_<< endl;
cout<<&dd .b1_<< endl;
cout<<&dd .b2_<< endl;
cout<<&dd .dd_<< endl;
p = (int **)ⅆ
cout<<p [0][0]<<endl;
cout<<p [0][1]<<endl;
cout<<endl ;
cout<<p [2][0]<<endl;
cout<<p [2][1]<<endl;
BB* pp ;
pp = &dd ;
dd.bb_ = 10; //對象的內存模型在編譯時就已經確定了,否則無法定義類的對象,因為要開闢內存
int base = pp-> bb_; // 通過間接訪問 (其實pp 已經偏移了20 ),這需要運行時的支持
cout<<"dd.bb_=" <<base<< endl;
return 0;
}
從輸出的地址和虛基類表成員數據可以畫出對象內存模型圖:
virtual base table
本類地址與虛基類表指針地址的差
虛基類地址與虛基類表指針地址的差
virtual base table pointer(vbptr)
從程序可以看出pp是BB* 指針,通過列印pp 的值與&dd 比較可知,
cout<<(void*)&dd<<endl;
cout<<(void*)pp<<endl;
pp實際上已經偏移了20個位元組,如何實現的呢?先找到首個vbptr,找到虛基類BB地址與虛基類表指針地址的差,也即是20,接著pp偏移20個位元組指向了dd對象中的BB部分,然後就訪問到了bb_,這是在運行時才做的轉換。記住:C++標準規定對對象取地址將始終為對應類型的首地址。
二、只出現虛函數的情況
(一):一般繼承
C++ Code
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
57
58
#include <iostream>
using namespace std;
class Base
{
public :
virtual void Fun1()
{
cout << "Base::Fun1 ..." << endl;
}
virtual void Fun2()
{
cout << "Base::Fun2 ..." << endl;
}
int data1_ ;
};
class Derived : public Base
{
public :
void Fun2 ()
{
cout << "Derived::Fun2 ..." << endl;
}
virtual void Fun3()
{
cout << "Derived::Fun3 ..." << endl;
}
int data2_ ;
};
typedef void (* FUNC)(void );
int main (void)
{
cout << sizeof (Base) << endl;
cout << sizeof (Derived) << endl;
Base b ;
int **p = (int **)& b;
FUNC fun = (FUNC) p[0][0];
fun();
fun = (FUNC )p[0][1];
fun();
cout << endl ;
Derived d ;
p = (int **)&d;
fun = (FUNC )p[0][0];
fun();
fun = (FUNC )p[0][1];
fun();
fun = (FUNC )p[0][2];
fun();
return 0;
}
從輸出的函數體可以畫出對象內存模型圖:
vtbl:虛函數表(存放虛函數的函數指針)
vptr:虛函數表指針
從輸出可以看出,Derived類繼承了Base::Fun1,而覆蓋了Fun2,此外還有自己的Fun3。注意,因為Fun3是虛函數,才會出現在虛函數表,如果是一般函數是不會的,因為不用通過vptr間接訪問。
(二)、鑽石繼承
C++ Code
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <iostream>
using namespace std;
class BB
{
public:
virtual void vpbb()
{
cout << "BB:vpbb().." << endl;
}
int bb_;
};
class B1 : public BB
{
public:
virtual void vpb1()
{
cout << "B1:vpb1().." << endl;
}
int b1_;
};
class B2 : public BB
{
public:
virtual void vpb2()
{
cout << "B2:vpb2().." << endl;
}
int b2_;
};
class DD : public B1, public B2
{
public:
virtual void vpdd()
{
cout << "DD:vpdd().." << endl;
}
int dd_;
};
typedef void (* FUNC)(void );
int main()
{
cout << sizeof(BB) << endl;
cout << sizeof(B1) << endl;
cout << sizeof(DD) << endl;
cout << endl;
DD dd ;
cout << &dd << endl;
cout << &dd.B1::bb_ << endl;
cout << &dd.B2::bb_ << endl;
cout << &dd .b1_ << endl;
cout << &dd .b2_ << endl;
cout << &dd .dd_ << endl;
cout << endl;
B1 b ;
int **p = (int **)& b;
FUNC fun = (FUNC) p[0][0];
fun();
fun = (FUNC )p[0][1];
fun();
cout << endl ;
p = (int **)&dd
fun = (FUNC)p[0][0];
fun();
fun = (FUNC)p[0][1];
fun();
fun = (FUNC)p[0][2];
fun();
fun = (FUNC)p[3][0];
fun();
fun = (FUNC)p[3][1];
fun();
cout << endl;
return 0;
}
從成員輸出的地址和通過虛函數表指針訪問到的函數可以畫出模型:
DD::vfdd 的位置跟繼承的順序有關,如果DD先繼承的是B2, 那麼它將跟在B2::vfb2 的下面。
如果派生類是從多個基類繼承或者有多個繼承分支(從所有根類開始算起),而其中若干個繼承分支上出現了多態類,則派生類將從這些分支中的每個分支上繼承一個vptr,編譯器也將為它生成多個vtable,有幾個vptr就生成幾個vtable(每個vptr分別指向其中一個),分別與它的多態基類對應。
三、虛繼承與虛函數同時出現的情況:
C++ Code
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <iostream>
using namespace std;
class BB
{
public :
virtual void vfbb()
{
cout<<"BB::vfbb" <<endl;
}
virtual void vfbb2()
{
cout<<"BB::vfbb2" <<endl;
}
int bb_ ;
};
class B1 : virtual public BB
{
public :
virtual void vfb1()
{
cout<<"B1::vfb1" <<endl;
}
int b1_ ;
};
class B2 : virtual public BB
{
public :
virtual void vfb2()
{
cout<<"B2::vfb2" <<endl;
}
int b2_ ;
};
class DD : public B1, public B2
{
public :
virtual void vfdd()
{
cout<<"DD::vfdd" <<endl;
}
int dd_ ;
};
typedef void (* FUNC)(void);
int main (void)
{
cout<<sizeof (BB)<< endl;
cout<<sizeof (B1)<< endl;
cout<<sizeof (DD)<< endl;
BB bb ;
int** p ;
p = (int **)&bb;
FUNC fun ;
fun = (FUNC )p[0][0];
fun();
fun = (FUNC )p[0][1];
fun();
cout<<endl ;
B1 b1 ;
p = (int **)&b1;
fun = (FUNC )p[0][0];
fun();
fun = (FUNC )p[3][0];
fun();
fun = (FUNC )p[3][1];
fun();
cout<<p [1][0]<<endl;
cout<<p [1][1]<<endl;
cout<<endl ;
DD dd ;
p = (int **)ⅆ
fun = (FUNC )p[0][0];
fun();
fun = (FUNC )p[0][1]; // DD::vfdd 掛在 B1::vfb1的下面
fun();
fun = (FUNC )p[3][0];
fun();
fun = (FUNC )p[7][0];
fun();
fun = (FUNC )p[7][1];
fun();
cout<<p [1][0]<<endl;
cout<<p [1][1]<<endl;
cout<<p [4][0]<<endl;
cout<<p [4][1]<<endl;
return 0;
}
從輸出的虛基類表成員數據和虛函數體可以畫出對象內存模型圖:
上圖中vfdd 出現的位置跟繼承的順序有關,如果DD先繼承的是B2,那麼它將跟在vfb2 的下面。
注意:如果沒有虛繼承,則虛函數表會合併,一個類只會存在一個虛函數表和一個虛函數表指針(同個類的對象共享),當然也不會有虛基類表和虛基類表指針的存在。
但如果是鑽石繼承,那麼是會存在兩份虛函數表和兩份虛函數表指針的。
※Wireshark實戰分析之ARP協議
※安卓okhttp3與伺服器通過json數據交互解析與上傳
TAG:程序員小新人學習 |