虚函数表

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。

首先先通过一个例子来引入虚函数表,假如现在有三个类如下:

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

class A //包含虚函数的类

{

public:

 virtual void func1()

 {}

 virtual void func2()

 {}

};

class B//空类

{};

class C //包含成员函数不包含成员变量的类

{

 void fun()

 {}

};

void Test1()

{

 cout << sizeof(A) << endl;

 cout << sizeof(B) << endl;

 cout << sizeof(C) << endl;

}

就上述的代码,将会分别输出4,1,1

造成A的大小为4的原因就是:在A中存放了一个指向A类的虚函数表的指针。而32位下一个指针大小为4字节,所以就为4。

A类实例化后在内存中对应如下:

注:在虚函数表中用0来结尾。

通过内存中的显示我们就能知道编译器应该将虚函数表的指针存在于对象实例中最前面的位置,所以可以&a转成int*,取得虚函数表的地址,再强转成(int*)方便接下来可以每次只访问四个字节大小(虚函数表可看做是一个函数指针数组,由于32位下指针是4字节,所以转为(int*))。将取得的int*指针传给下面的打印虚函数表的函数,就能够打印出对应的地址信息。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

typedef void(*FUNC) ();

//int*VTavle = (int*)(*(int*)&a)

//传参完成后就可打印出对应的信息。

void PrintVTable(int* VTable)

{

 cout << " 虚表地址>" << VTable << endl;

 for (int i = 0; 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

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

class A

{

public:

 virtual void func1()

 {

 cout << "A::func1" << endl;

 }

 virtual void func2()

 {

 cout << "A::func2" << endl;

 }

public:

 int _a;

};

class B : public A

{

public:

 virtual void func3()

 {

 cout << "B::func3" << endl;

 }

 virtual void func4()

 {

 cout << "B::func4" << endl;

 }

public:

 int _b;

};

void Test1()

{

 B b;

 b._a = 1;

 b._b = 2;

 int* VTable = (int*)(*(int*)&b);

 PrintVTable(VTable);

}

将内存中的显示和我们写的显示虚函数表对应起来如下:

小结:

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。(由于子类单继承父类,直接使用父类的虚函数表)

一般继承(成员变量+虚函数覆盖)

在上面例子进行稍微修改,使得子类中有对父类虚函数的覆盖,进行和之前同样的测试:

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

class A

{

public:

 virtual void func1()

 {

 cout << "A::func1" << endl;

 }

 virtual void func2()

 {

 cout << "A::func2" << endl;

 }

public:

 int _a;

};

class B : public A

{

public:

 virtual void func1()

 {

 cout << "B::func1" << endl;

 }

 virtual void func3()

 {

 cout << "B::func3" << endl;

 }

public:

 int _b;

};

小结:

1)覆盖的func()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

多重继承(成员变量+虚函数覆盖)

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

class A

{

public:

 virtual void func1()

 {

 cout << "A::func1" << endl;

 }

 virtual void func2()

 {

 cout << "A::func2" << endl;

 }

public:

 int _a;

};

class B

{

public:

 virtual void func3()

 {

 cout << "B::func1" << endl;

 }

public:

 int _b;

};

class C : public A , public B

{

 //覆盖A::func1()

 virtual void func1()

 {

 cout << "C::func1()"<<endl;

 }

 virtual void func4()

 {

 cout << "C::func4()" << endl;

 }

public:

 int _c;

};

再次调试观察:

小结:

多重继承后的子类将与自己第一个继承的父类公用一份虚函数表。(上述例子中A为C的第一个继承类)

菱形继承(成员变量 + 虚函数覆盖)

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

class A

{

public:

 virtual void func1()

 {

 cout << "A::func1" << endl;

 }

public:

 int _a;

};

class B : public A

{

public:

 virtual void func2()

 {

 cout << "B::func2" << endl;

 }

public:

 int _b;

};

class C : public A

{

 virtual void func3()

 {

 cout << "C::func3()" << endl;

 }

public:

 int _c;

};

class D : public B , public C

{

 virtual void func2()

 {

 cout << "D::fun2()" << endl;

 }

 virtual void func4()

 {

 cout << "D::fun4()" << endl;

 }

public:

 int _d;

};

void Test1()

{

 D d;

 d.B::_a = 1;

 d.C::_a = 2;

 d._b = 3;

 d._c = 4;

 d._d = 5;

 int* VTable = (int*)(*(int*)&d);

 PrintVTable(VTable);

}

掌握了单继承和多继承的规律,按照总结的一步步分析,就可以最终得到D的虚函数表。

由于子类B继承父类A,所以B与A公用一个虚函数表,又因为B是D多继承中的第一个继承的类,所以B,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

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

class A

{

public:

 virtual void func1()

 {

 cout << "A::func1()" << endl;

 }

 virtual void func2()

 {

 cout << "A::func2()" << endl;

 }

public:

 int _a;

};

class B : virtual public A//虚继承A,覆盖func1()

{

public:

 virtual void func1()

 {

 cout << "B::func1()" << endl;

 }

 virtual void func3()

 {

 cout << "B::func3()" << endl;

 }

public:

 int _b;

};

class C : virtual public A //虚继承A,覆盖func1()

{

 virtual void func1()

 {

 cout << "C::func1()" << endl;

 }

 virtual void func3()

 {

 cout << "C::func3()" << endl;

 }

public:

 int _c;

};

class D : public B , public C//虚继承B,C,覆盖func1()

{

 virtual void func1()

 {

 cout << "D::func1()" << endl;

 }

 virtual void func4()

 {

 cout << "D::func4()" << endl;

 }

public:

 int _d;

};

typedef void(*FUNC) ();

void PrintVTable(int* VTable)

{

 cout << " 虚表地址>" << VTable << endl;

 for (int i = 0; VTable[i] != 0; ++i)

 {

 printf(" 第%d个虚函数地址 :0X%x,->", i, VTable[i]);

 FUNC f = (FUNC)VTable[i];

 f();

 }

 cout << endl;

}

void Test1()

{

 D d;

 d.B::_a = 1;

 d.C::_a = 2;

 d._b = 3;

 d._c = 4;

 d._d = 5;

 cout <<"sizeof(A) = "<< sizeof(A) << endl;

 cout << "sizeof(B) = " << sizeof(B) << endl;

 cout << "sizeof(C) = " << sizeof(C) << endl;

 //打印d的虚函数表

 int* VTable = (int*)(*(int*)&d);

 PrintVTable(VTable);

 //打印C的虚函数表

 VTable = (int*)*(int*)((char*)&d + sizeof(B)-sizeof(A));

 PrintVTable(VTable);

 //打印A的虚函数表

 VTable = (int*)*(int*)((char*)&d + sizeof(B)+sizeof(C)-2*sizeof(A)+4);

 PrintVTable(VTable);

}

接下来就慢慢分析:

1)先通过调试查看内存中是如何分配的,并和我们打印出的虚函数表对应起来:

注:由于B,C是虚继承A,所以编译器为了解决菱形继承所带来的“二义性”以及“数据冗余”,便将A放在最末端,并在子类中存放一个虚基表,方便找到父类;而虚基表的前四个字节存放的是对于自己虚函数表的偏移量,再往下四个字节才是对于父类的偏移量。

2)接下来就抽象出来分析模型

总结

1)虚函数按照其声明顺序放于表中;

2)父类的虚函数在子类的虚函数前面(由于子类单继承父类,直接使用父类的虚函数表);

3)覆盖的func()函数被放到了虚表中原来父类虚函数的位置;

4)没有被覆盖的函数依旧;

5)如果B,C虚继承A,并且B,C内部没有再声明或定义虚函数,则B,C没有对应的虚函数表;

6)在菱形的虚拟继承中,要注意A为B,C所共有的,在打印对应虚函数表时要注意偏移量。

C++对象继承中的内存布局相关推荐

  1. 7. 重磅硬核 | 一文聊透对象在JVM中的内存布局,以及内存对齐和压缩指针的原理及应用

    重磅硬核 | 一文聊透对象在JVM中的内存布局,以及内存对齐和压缩指针的原理及应用 大家好,我是bin,又到了每周我们见面的时刻了,我的公众号在1月10号那天发布了第一篇文章?<从内核角度看IO ...

  2. 一文聊透对象在JVM中的内存布局,以及内存对齐和压缩指针的原理及应用

    大家好,我是bin,又到了每周我们见面的时刻了,我在1月10号那天发布了第一篇文章<从内核角度看IO模型的演变>,在这篇文章中我们通过图解的方式以一个C10k的问题为主线,从内核角度详细阐 ...

  3. java 句柄池_深入理解JVM之Java对象的创建、内存布局、访问定位详解

    本文实例讲述了深入理解JVM之Java对象的创建.内存布局.访问定位.分享给大家供大家参考,具体如下: 对象的创建 一个简单的创建对象语句Clazz instance = new Clazz();包含 ...

  4. java 对象压缩_理解Java对象:要从内存布局及底层机制说起,话说....

    前言 大家好,又见面了,今天是JVM专题的第二篇文章,在上一篇文章中我们说了Java的类和对象在JVM中的存储方式,并使用HSDB进行佐证,没有看过上一篇文章的小伙伴可以点这里:< 这篇文章主要 ...

  5. c30-程序中的三国天下(c31-程序中的内存布局)

    程序中的栈 程序中的堆 程序中的静态存储区 小结 程序中的内存布局 1.程序文件的一般布局 初始化的        全局变量,静态变量   存储在     .data  section 未初始化的  ...

  6. JVM:对象的实例化、内存布局与访问定位

    对象的实例化 创建对象的方式 new关键字 最常见的方式 变形1:Xxx的静态方法(单例模式) 变形2:XxxBuilder/XxxFactory的静态方法 Class的newInstance() 反 ...

  7. Java对象的创建、内存布局和访问定位

    在Java运行时数据区中,我们知道了虚拟机内存的概况,本文介绍虚拟机内存中的数据的其它细节,如对象如何创建.如何布局以及如何访问. 基于实用的原则,这里以HotSpot虚拟机和常用的内存区域Java堆 ...

  8. 【深入理解JVM】:Java对象的创建、内存布局、访问定位

    对象的创建 一个简单的创建对象语句Clazz instance = new Clazz();包含的主要过程包括了类加载检查.对象分配内存.并发处理.内存空间初始化.对象设置.执行ini方法等. 主要流 ...

  9. 理解Java对象:要从内存布局及底层机制说起,话说....

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 前言 大家好,又见面了,今天是JVM专题的第二篇文章,在上 ...

最新文章

  1. Yolov5总结文档(理论、代码、实验结果)
  2. java命令行生成jar_命令行生成可执行的jar包
  3. 从库中图片逐帧插入到场景
  4. 【Android 应用开发】Android - 按钮组件详解
  5. C语言sprintf函数(发送格式化输出到 str 所指向的字符串)(format 标签属性)(字符串拼接)(数字转字符串、浮点数转字符串)
  6. git 如何删除本地创建的仓库(转载自 https://segmentfault.com/q/1010000002996177?_ea=262685)...
  7. pc寄存器or程序计数器
  8. 为什么有些内联(行内)元素可以设置宽高?
  9. Linux基础——操作系统框架
  10. Npm 恶意包试图窃取 Discord 敏感信息和浏览器文件
  11. beanstalkd mysql_beanstalkd 安装和配置
  12. NLP系列之文本分类
  13. python 弹窗炸弹
  14. stmt php,PHP mysqli_stmt_free_result() 函数用法及示例
  15. python经纬度转换xy坐标公式 pyqt_EXCEL公式进行经纬度与XY坐标的相互转换
  16. Android图形绘制之——简单的几何图形
  17. Git清理历史大文件
  18. 《矩阵理论与方法》lambda矩阵及Jordan标准形
  19. GameMaker: Studio 学习笔记(二)深度 物理属性 房间切换 滑冰
  20. 【论文精读】Deep Rectangling for Image Stitching: A Learning Baseline

热门文章

  1. jni c向java传递数组_通过jni将jint数组从c返回到java
  2. MFC 基础知识:主对话框与子对话框(一)
  3. 了解单片机及单片机的控制原理和 DX516 的用法,控制一个 LED 灯的亮
  4. 信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言——1078:求分数序列和
  5. ROS通信架构(上)
  6. 【机器视觉】 exit算子
  7. 【Linux网络编程】套接字简介
  8. 【Protocol Buffer】Protocol Buffer入门教程(六):枚举和包
  9. 【Linux】一步一步学Linux——groups命令(93)
  10. java类型比较_Java数据类型的比较