當前位置:
首頁 > 知識 > C++虛繼承 虛函數對C++對象內存模型造成的影響(類/對象大小)

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 **)&dd;

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;

}

C++虛繼承 虛函數對C++對象內存模型造成的影響(類/對象大小)

從輸出的地址和虛基類表成員數據可以畫出對象內存模型圖:

virtual base table


本類地址與虛基類表指針地址的差

虛基類地址與虛基類表指針地址的差

virtual base table pointer(vbptr)

C++虛繼承 虛函數對C++對象內存模型造成的影響(類/對象大小)

從程序可以看出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:虛函數表指針

C++虛繼承 虛函數對C++對象內存模型造成的影響(類/對象大小)

從輸出可以看出,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;

}

C++虛繼承 虛函數對C++對象內存模型造成的影響(類/對象大小)

從成員輸出的地址和通過虛函數表指針訪問到的函數可以畫出模型:

C++虛繼承 虛函數對C++對象內存模型造成的影響(類/對象大小)

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 **)&dd;

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;

}

C++虛繼承 虛函數對C++對象內存模型造成的影響(類/對象大小)

從輸出的虛基類表成員數據和虛函數體可以畫出對象內存模型圖:

C++虛繼承 虛函數對C++對象內存模型造成的影響(類/對象大小)

上圖中vfdd 出現的位置跟繼承的順序有關,如果DD先繼承的是B2,那麼它將跟在vfb2 的下面。

注意:如果沒有虛繼承,則虛函數表會合併,一個類只會存在一個虛函數表和一個虛函數表指針(同個類的對象共享),當然也不會有虛基類表和虛基類表指針的存在。

但如果是鑽石繼承,那麼是會存在兩份虛函數表和兩份虛函數表指針的。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

Wireshark實戰分析之ARP協議
安卓okhttp3與伺服器通過json數據交互解析與上傳

TAG:程序員小新人學習 |