news 2026/4/27 9:40:47

C++虚函数表与类的内存分布深入分析理解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++虚函数表与类的内存分布深入分析理解

不可定义为虚函数的函数

类的静态函数和构造函数不可以定义为虚函数:

静态函数的目的是通过类名+函数名访问类的static变量,或者通过对象调用staic函数实现对static成员变量的读写,要求内存中只有一份数据。而虚函数在子类中重写,并且通过多态机制实现动态调用,在内存中需要保存不同的重写版本。

构造函数的作用是构造对象,而虚函数的调用是在对象已经构造完成,并且通过调用时动态绑定。动态绑定是因为每个类对象内部都有一个指针,指向虚函数表的首地址。而且虚函数,类的成员函数,static成员函数都不是存储在类对象中,而是在内存中只保留一份。

将析构函数定义为虚函数的作用

类的构造函数不能定义为虚函数,析构函数可以定义为虚函数,这样当我们delete一个指向子类对象的基类指针时可以达到调用子类析构函数的作用,从而动态释放内存。

如下我们先定义一个基类和子类

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

classVirtualTableA

{

public:

virtual~VirtualTableA()

{

cout <<"Desturct Virtual Table A"<< endl;

}

virtualvoidprint()

{

cout <<"print virtual table A"<< endl;

}

};

classVirtualTableB :publicVirtualTableA

{

public:

virtual~VirtualTableB()

{

cout <<"Desturct Virtual Table B"<< endl;

}

virtualvoidprint();

};

voidVirtualTableB::print()

{

cout <<"this is virtual table B"<< endl;

}

我们写一个函数做测试

1

2

3

4

5

6

7

8

9

10

11

voiddestructVirtualTable()

{

VirtualTableA *pa =newVirtualTableB();

useTable(pa);

deletepa;

}

voiduseTable(VirtualTableA *pa)

{

//实现动态调用

pa->print();

}

程序输出

this is virtual table B
Desturct Virtual Table B
Desturct Virtual Table A

在上面的例子中我们先在destructVirtualTable函数中new了一个VirtualTableB类型对象,并用基类VirtualTableA的指针指向了这个对象。

然后将基类指针对象pa传递给useTable函数,这样会根据多态原理调用VirtualTableB的print函数,然后再执行delete pa操作。

此时如果pa的析构函数不写成虚函数,那么就只会调用VirtualTableA的析构函数,不会调用子类VirtualTableB的析构函数,导致内存泄露。

而我们将析构函数写成虚析构之后,可以看到先调用了子类VirtualTableB的析构函数,再调用了基类VirtualTableA的析构函数,达到了释放子类空间的目的。

有人会问?将析构函数不写为虚函数,直接delete子类对象VirtualTableB,调用子类的析构函数不可以吗?比如,如下的调用

1

2

VirtualTableB *pb =newVirtualTableB();

deletepa;

上述调用没有问题,无论析构函数是否为虚析构都可以成功释放子类空间。但是项目编程中常常会编写一些通用接口,比如上面的useTable函数,

它只接受VirtualTableA类型的指针,所以我们常常会用基类指针接受子类对象来通过多态的方式调用子类函数,为了方便delete基类指针也要释放子类空间,

就要将析构函数设置为虚函数。

虚函数表原理

为了介绍虚函数表原理,我们先实现一个基类和子类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

classBaseclass

{

public:

Baseclass() : a(1024) {}

virtualvoidf() { cout <<"Base::f"<< endl; }

virtualvoidg() { cout <<"Base::g"<< endl; }

virtualvoidh() { cout <<"Base::h"<< endl; }

inta;

};

// 0 1 2 3 4 5 6 7(虚函数表空间) 8 9 10 11 12 13 14 15(存储的是a)

classDeriveClass :publicBaseclass

{

public:

virtualvoidf() { cout <<"Derive::f"<< endl; }

virtualvoidg2() { cout <<"Derive::g2"<< endl; }

virtualvoidh3() { cout <<"Derive::h3"<< endl; }

};

一个类对象其内存分布的基本结构为虚函数表地址+非静态成员变量,类的成员函数不占用类对象的空间,他们分布在一片属于类的共有区域。

类的静态成员函数喝成员变量不占用类对象的空间,他们分配在静态区。

虚函数表的地址存储在类对象的起始位置。所以我们利用这个原理,通过寻址的方式访问虚函数表里的函数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

voiduseVitualTable()

{

Baseclass b;

b.a = 1024;

cout <<"sizeof b is "<<sizeof(b) << endl;

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

cout <<"pointer address of vitural table "<< p << endl;

cout <<"address of b is "<< &b << endl;

cout <<"address of a is "<< p + 2 << endl;

cout <<"address of p+1 is "<< p +1 << endl;

cout <<"value of a is "<< *(p + 2) << endl;

cout <<"address of vitural table"<< (int*)(*p) << endl;

cout <<"sizeof int is "<<sizeof(int) << endl;

cout <<"sizeof p is "<<sizeof(p) <<" sizeof(int*) is "<<sizeof(int*) << endl;

Func pFun = (Func)(*(int*)(*p));

pFun();

pFun = (Func) * ((int*)(*p) + 2);

pFun();

pFun = (Func)(*((int*)(*p) + 4));

pFun();

}

上面的程序输出

sizeof b is 16
pointer address of vitural table 0xb6fdd0
address of b is 0xb6fdd0
address of a is 0xb6fdd8
address of p+1 is 0xb6fdd4
value of a is 1024
address of vitural table0x46d890
sizeof int is 4
sizeof p is 8 sizeof(int*) is 8
Base::f
Base::g
Base::h

可以看到b的大小为16字节,因为我的机器是64位的,所以指针类型都占用8字节,int 占用4字节,但是要遵循补齐原则,结构体的大小要为最大成员大小的整数倍,所以要补齐4字节,那么8+4+4 = 16 字节,关于类对象对齐和补齐原则稍后再详述。

b的内存分布如下图

这个根据不同的机器所占的字节数不一样,在32位机器上int为4字节,虚函数表地址为4字节,4+4 = 8字节,这个再之后再说明对齐和补齐的原则。

&b表示取b的地址,因为虚函数表地址存储在b的起始地址,所以&b也是虚函数表的地址的地址,我们通过int*强转是方便存储b的地址,因为64位机器指针都是8字节,32位机器指针是4字节。

p为虚函数表的地址的地址,p+1具体移动了4个字节,因为p+1移动多少个字节取决于p所指向的数据类型int,int为4字节,所以p+1在p的地址移动四个字节,p+2在p的地址移动8个字节。

p只想虚函数表的地址,换句话说p存储的是虚函数表的地址,虚函数表地址占用8字节,p+2就是从p向后移动8字节,这样刚好找到a的地址。

那么*(p+2)就是取a的数值。

int*(*p)就是取虚函数表的地址,转为int*是方便读写。

我们将b的内存分布以及虚函数表结构画出来

上图中可以看到虚函数表中存储的是虚函数的地址,所以通过不断位移虚函数表的指针就可以达到指向不同虚函数的目的。

1

2

Func pFun = (Func)(*(int*)(*p));

pFun();

*(int *)(*p)就是取出虚函数表首地址指向的虚函数,再通过Func转化为函数类型,然后调用pFun即可调用虚函数f。

所以想调用第二个虚函数g,将(int*)(*p)加2 位移8个字节即可

1

2

pFun = (Func) * ((int*)(*p) + 2);

pFun();

同样的道理调用h就不赘述了。

继承关系中虚函数表结构

DeriveClass继承了BaseTest类,子类如果重写了虚函数,则子类的虚函数表中存储的虚函数为子类重写的,否则为基类的。

我们画一下DeriveClass的虚函数表结构

因为函数f被DeriveClass重写,所以DeriveClass的虚函数表存储的是自己重写的f。

而虚函数g和h没有被DeriveClass重写,所以DeriveClass虚函数表存储的是基类的g和h。

另外DeriveClass虚函数表里也存储了自己特有的虚函数g2和h3.

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/27 9:40:05

Venera漫画管理系统文件导入全攻略

Venera漫画管理系统文件导入全攻略 在数字阅读时代&#xff0c;漫画爱好者们需要一个高效便捷的管理工具来整理自己的收藏。Venera作为一款专业的漫画管理应用&#xff0c;其文件导入功能为您的漫画库建设提供了强有力的支持。 &#x1f680; 快速入门&#xff1a;三步完成漫…

作者头像 李华
网站建设 2026/4/27 9:39:04

Lean3数学库实战:从简单定理到复杂数学问题求解

Lean3数学库实战&#xff1a;从简单定理到复杂数学问题求解 【免费下载链接】lean3 Lean Theorem Prover 项目地址: https://gitcode.com/gh_mirrors/le/lean3 Lean3作为一款强大的定理证明器&#xff0c;其数学库为从基础逻辑到高等数学的问题求解提供了完整的形式化支…

作者头像 李华
网站建设 2026/4/27 9:34:27

量化交易WebSocket统一接口:ic-py库的设计原理与实战应用

1. 项目概述&#xff1a;一个为量化交易而生的Python工具库如果你在量化交易领域摸爬滚打过一段时间&#xff0c;尤其是在处理加密货币或传统金融市场的实时数据时&#xff0c;一定会对“连接”这件事感到头疼。市面上的交易所API五花八门&#xff0c;每个都有自己的SDK、认证方…

作者头像 李华
网站建设 2026/4/27 9:32:21

实战:在eNSP中配置基于MAC地址的VLAN,实现设备移动网络自动跟随

实战&#xff1a;在eNSP中配置基于MAC地址的VLAN&#xff0c;实现设备移动网络自动跟随 现代办公环境中&#xff0c;设备移动性需求日益突出。想象这样一个场景&#xff1a;公司高管的笔记本电脑需要始终接入管理VLAN&#xff0c;而访客的平板电脑则应该自动分配到访客VLAN&…

作者头像 李华
网站建设 2026/4/27 9:31:20

STM32CubeIDE定时器PWM实战:从驱动舵机到控制电机转速,一份配置通吃

STM32CubeIDE定时器PWM实战&#xff1a;从驱动舵机到控制电机转速 在嵌入式开发中&#xff0c;PWM&#xff08;脉冲宽度调制&#xff09;技术就像一位无声的指挥家&#xff0c;精确控制着各种执行器的动作节奏。无论是机器人关节的灵活转动&#xff0c;还是无人机螺旋桨的稳定转…

作者头像 李华
网站建设 2026/4/27 9:27:13

AMD Ryzen处理器调试终极指南:掌握硬件级性能调优完整教程

AMD Ryzen处理器调试终极指南&#xff1a;掌握硬件级性能调优完整教程 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https:…

作者头像 李华