首先重新回顾一下关于类/对象大小的计算原则:

类大小计算遵循结构体对齐原则

第一个数据成员放在offset为0的位置

其它成员对齐至min(sizeof(member),#pragma pack(n)所指定的值)的整数倍。

整个结构体也要对齐,结构体总大小对齐至各个成员中最大对齐数的整数倍。

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 [
][
]<<endl;

      cout<<p [
][
]<<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 [
][
]<<endl;

      cout<<p [
][
]<<endl;

      cout<<endl ;

      cout<<p [
][
]<<endl;

      cout<<p [
][
]<<endl;

BB* pp ;

pp = &dd ;

      dd.bb_ = 

//对象的内存模型在编译时就已经确定了,否则无法定义类的对象,因为要开辟内存
      
int base = pp-> bb_;     
// 通过间接访问 (其实pp 已经偏移了20 ),这需要运行时的支持
      cout<<
"dd.bb_=" <<base<< endl;

return 
;

}

从输出的地址和虚基类表成员数据可以画出对象内存模型图:

virtual base table

本类地址与虚基类表指针地址的差

虚基类地址与虚基类表指针地址的差

virtual base table pointer(vbptr)

从程序可以看出pp是BB* 指针,pp首先指向dd内存,当执行pp->bb_时,先找到首个vbptr,找到虚基类BB地址与虚基类表指针地址的差,也即是20,接着pp偏移20个字节指向了dd对象中的BB部分,然后就访问到了bb_,这是在运行时才做的转换。

二、只出现虚函数的情况

 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[
][
];

    fun();

    fun = (FUNC )p[
][
];

    fun();

    cout << endl ;

Derived d ;

    p = (
int **)&d;

    fun = (FUNC )p[
][
];

    fun();

    fun = (FUNC )p[
][
];

    fun();

    fun = (FUNC )p[
][
];

    fun();

return 
;

}

从输出的函数体可以画出对象内存模型图:

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

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[
][
];

      fun();

      fun = (FUNC )p[
][
];

      fun();

      cout<<endl ;

B1 b1 ;

     

      p = (
int **)&b1;

      fun = (FUNC )p[
][
];

      fun();

      fun = (FUNC )p[
][
];

      fun();

      fun = (FUNC )p[
][
];

      fun();

cout<<p [
][
]<<endl;

      cout<<p [
][
]<<endl;

      cout<<endl ;

DD dd ;

      p = (
int **)&dd;

      fun = (FUNC )p[
][
];

      fun();

      fun = (FUNC )p[
][
]; 
// DD::vfdd 挂在 B1::vfb1的下面
      fun();

      fun = (FUNC )p[
][
];

      fun();

      fun = (FUNC )p[
][
];

      fun();

      fun = (FUNC )p[
][
];

      fun();

     

      cout<<p [
][
]<<endl;

      cout<<p [
][
]<<endl;

      cout<<p [
][
]<<endl;

      cout<<p [
][
]<<endl;

return 
;

}

从输出的虚基类表成员数据和虚函数体可以画出对象内存模型图:

注意:如果没有虚继承,则虚函数表会合并,一个类只会存在一个虚函数表和一个虚函数表指针(同个类的对象共享),当然也不会有
虚基类表和虚基类表指针的存在。

参考:

C++ primer 第四版
Effective C++ 3rd
C++编程规范

从零开始学C++之虚继承和虚函数对C++对象内存模型造成的影响的更多相关文章

  1. c++虚继承与虚函数

    学习继承与多态时看到这两个概念,记录整理. 虚继承与虚函数都是用virtual关键字实现,虚继承为了防止多重继承,而虚函数为了实现多态. 是几个例子. 虚继承: class A{}; class B: ...

  2. C++多重继承分析——《虚继承实现原理(虚继承和虚函数)》

    博客转载:https://blog.csdn.net/longlovefilm/article/details/80558879 一.虚继承和虚函数概念区分 虚继承和虚函数是完全无相关的两个概念. 虚 ...

  3. C++对象内存模型2 (虚函数,虚指针,虚函数表)

    从例子入手,考察如下带有虚函数的类的对象内存模型: class A { public: virtual void vfunc1(); virtual void vfunc2(); void func1 ...

  4. C++虚函数、虚继承、对象内存模型(转)

    参考:http://blog.csdn.net/hxz_qlh/article/details/14633361 需要注意的是虚继承.多重继承时类的大小.

  5. C++基础 (6) 第六天 继承 虚函数 虚继承 多态 虚函数

    继承是一种耦合度很强的关系 和父类代码很多都重复的 2 继承的概念 3 继承的概念和推演 语法: class 派生类:访问修饰符 基类 代码: … … 4 继承方式与访问控制权限 相对的说法: 爹派生 ...

  6. C++对象内存模型2 (虚函数,虚指针,虚函数表)(转)

    class A { public: virtual void vfunc1(); virtual void vfunc2(); void func1(); void func2(); virtual ...

  7. 从零开始学 Web 之 JavaScript(三)函数

    大家好,这里是「 Daotin的梦呓 」从零开始学 Web 系列教程.此文首发于「 Daotin的梦呓 」公众号,欢迎大家订阅关注.在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识 ...

  8. C++学习笔记----4.5 C++继承时的对象内存模型

    推荐阅读:http://blog.csdn.net/randyjiawenjie/article/details/6693337 最近研究了一下,C++继承的内存对象模型.主要是读了读http://b ...

  9. 记录:C++类内存分布(虚继承与虚函数)

    工具:VS2013 先说一下VS环境下查看类内存分布的方法: 先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存 ...

随机推荐

  1. 高性能双端js模板---simplite

    simplite是一款js实现的模板引擎,它能够完成浏览器端js模版和node服务器端js模板的数据渲染,渲染性能达到引擎的极限. 渲染性能十分突出. 支持浏览器端和node服务器端模板渲染. 它简单 ...

  2. 微信应用号开发知识贮备之altjs官方实例初探

    天地会珠海分舵注:随着微信应用号的呼之欲出,相信新一轮的APP变革即将发生.从获得微信应用号邀请的业内人士发出来的一张开发工具源码截图可以看到,reacjs及其相应的FLUX框架altjs很有可能会成 ...

  3. jquery插件分类与编写详细讲解

    jquery插件分类与编写详细讲解 1. 插件种类 插件其实就是对现有的方法(或者叫函数)做一个封装,方便重用提高开发效率.   jQeury主要有2种类型   1)实例对象方法插件 开发能让所有的j ...

  4. 在Eclipse发展Webapp部署过程,缓存的位置

    介绍: 在Eclipse进行Web发展,通常直接在项目Eclipse集成Tomcat发展.那Webapp部署在那里?是否在高速缓冲存储器的位置,可以切换? 1.  查看当前的Webapp项目缓存位置 ...

  5. SqlServer-COMPUTE BY

    原文:SqlServer-COMPUTE BY COMPUTE BY子句可以通过同一个select语句即查看明细行,又查看汇总行.可以计算子组的汇总值,也可以计算整个结果集的汇总值 COMPUTE 子 ...

  6. C# 文件下载类

    using System; using System.Net; using System.IO; using System.Text; using System.Web; using System.W ...

  7. 用批处理编译*.sln工程

    原文:用批处理编译*.sln工程 批处理是直接调用Microsoft Visual Studio 8\Common7\IDE\ 目录内的 devenv.exe ,它启动后就是IDE,提供的参数如下: ...

  8. Ubuntu12.04环境搭建遇到的问题和建议(一个)

    后的新公司需要在Ubuntu12.04在结构Android开发环境,在这个过程中,我们还是会遇到很多问题,这里记录.为了方便自己的未来,有人谁需要参考.从网络! 1. Q:在终端: sudo apt- ...

  9. .NET Framework和C#版本历史概览

    发布日期 .Net版本 C#版本 CLR版本 开发工具 功能介绍 2002 1.0 1.0 初始版本 Visual Studio .Net 初始版本 .NET框架结构,详见: 2003 1.1     ...

  10. JAVA学习:异常

    一.异常官方定义: 1.就是不正常.程序在运行时出现的不正常情况.其实就是程序中出现的问题.这个问题按照面向对象思想进行描述,并封装成了对象.因为问题的产生有产生的原因.有问题的名称.有问题的描述等多 ...