C++类内存分布——深度理解继承与虚函数
来源:互联网 发布:淘宝店铺如何查排名 编辑:程序博客网 时间:2024/06/05 23:19
1.前言与准备
工欲善其事,必先利其器,我们先用好Visual Studio工具,像下面这样一步一步来:
先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局。近期的VS版本都支持这样配置。
2.单继承实验
下面可以定义一个类,像下面这样:
class Base{ int a; int b;public: void CommonFunction();};然后编译一下,可以看到输出框里面有这样的排布:
这里不想花精力在内存对齐因素上,所以成员变量都设为int型。从这里可以看到普通类的排布方式,成员变量依据声明的顺序进行排列(类内偏移为0开始),成员函数不占内存空间。
再看下继承,往后面添加如下代码:
class DerivedClass: public Base{ int c;public: void DerivedCommonFunction();};编译,然后看到如下的内存分布(父类的内存分布不变,这里只讨论子类成员变量的内存分布):
可以看到子类继承了父类的成员变量,在内存排布上,先是排布了父类的成员变量,接着排布子类的成员变量,同样,成员函数不占字节。
下面给基类加上虚函数,暂时注释掉DerivedClass,看一下这时的内存排布:class Base{ int a; int b;public: void CommonFunction(); void virtual VirtualFunction();};这个内存结构图分成了两个部分,上面是内存分布,下面是虚表,我们逐个看。VS所带编译器是把虚表指针放在了内存的开始处(0地址偏移),然后再是成员变量;下面生成了虚表,紧跟在&Base1_meta后面的0表示,这张虚表对应的虚指针在内存中的分布,下面列出了虚函数,左侧的0是这个虚函数的序号,这里只有一个虚函数,所以只有一项,如果有多个虚函数,会有序号为1,为2的虚函数列出来。
编译器是在构造函数创建这个虚表指针以及虚表的。
那么编译器是如何利用虚表指针与虚表来实现多态的呢?
是这样的,当创建一个含有虚函数的父类的对象时,编译器在对象构造时将虚表指针指向父类的虚函数;同样,当创建子类的对象时,编译器在构造函数里将虚表指针(子类只有一个虚表指针,它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)。
所以,如果是调用Base *p = new Derived();生成的是子类的对象,在构造时,子类对象的虚指针指向的是子类的虚表,接着由Derived*到Base*的转换并没有改变虚表指针,所以这时候p->VirtualFunction,实际上是p->vfptr->VirtualFunction,它在构造的时候就已经指向了子类的VirtualFunction,所以调用的是子类的虚函数,这就是多态了。
#########################################################
下面加上子类,并在子类中添加虚函数,像下面这样:
class DerivedClass: public Base{ int c;public: void DerivedCommonFunction(); void virtual VirtualFunction();};
可以看到子类内存的排布如下:
上半部是内存分布,可以看到,虚表指针被继承了,且仍位于内存排布的起始处,下面是父类的成员变量a和b,最后是子类的成员变量c,注意虚表指针只有一个,子类并没有再生成虚表指针了;下半部的虚表情况与父类是一样的。
#######################################################
我们把子类换个代码,像这样:
class DerivedClass1 : public Base{ int c;public: void DerivedCommonFunction(); void virtual VirtualFunction2();};注意到这时我们并没有覆写父类的虚方法,而是重声明了一个新的子类虚方法,内存分布如下:
还是只有一个虚表指针,但是下方虚表的内容变化了,虚表的0号是父类的VirtualFunction,而1号放的是子类的VirtualFunction2。也就是说,如果定义了DerivedClass的对象,那么在构造时,虚表指针就会指向这个虚表,以后如果调用的是VirtualFunction,那么会从父类中寻找对应的虚函数,如果调用的是VirtualFunction2,那么会从子类中寻找对应的虚函数。
3.多继承实验
下面来讨论多重继承,代码如下:
class Base{ int a; int b;public: void CommonFunction(); void virtual VirtualFunction();};class DerivedClass1: public Base{ int c;public: void DerivedCommonFunction(); void virtual VirtualFunction();};class DerivedClass2 : public Base{ int d;public: void DerivedCommonFunction(); void virtual VirtualFunction();};class DerivedDerivedClass : public DerivedClass1, public DerivedClass2{ int e;public: void DerivedDerivedCommonFunction(); void virtual VirtualFunction();};内存分布从父类到子类,依次如下:
Base中有一个虚表指针,地址偏移为0。DerivedClass1继承了Base,内存排布是先父类后子类。
DerivedClass2的情况是类似于DerivedClass1的。
下面我们重点看看这个类DerivedDerivedClass,由外向内看,它并列地排布着继承而来的两个父类DerivedClass1与DerivedClass2,还有自身的成员变量e。DerivedClass1包含了它的成员变量c,以及Base,Base有一个0地址偏移的虚表指针,然后是成员变量a和b;DerivedClass2的内存排布类似于DerivedClass1,注意到DerivedClass2里面竟然也有一份Base。
这里有两份虚表了,分别针对DerivedClass1与DerivedClass2,在&DerivedDericedClass_meta下方的数字是首地址偏移量,靠下面的虚表的那个-16表示指向这个虚表的虚指针的内存偏移,这正是DerivedClass2中的{vfptr}在DerivedDerivedClass的内存偏移。
4.总结
虚继承的作用是减少了对基类的重复,代价是增加了虚表指针的负担(更多的虚表指针)。
下面总结一下(当基类有虚函数时):
1. 每个类都有虚指针和虚表;
2. 如果不是虚继承,那么子类将父类的虚指针继承下来,并指向自身的虚表(发生在对象构造时)。有多少个虚函数,虚表里面的项就会有多少。多重继承时,可能存在多个的基类虚表与虚指针;
3. 如果是虚继承,那么子类会有两份虚指针,一份指向自己的虚表,另一份指向虚基表,多重继承时虚基表与虚基表指针有且只有一份。
- C++类内存分布——深度理解继承与虚函数
- 【C++】虚函数和虚继承的内存分布情况
- C++ 类 内存分布 虚函数 单继承 多继承
- C++ 虚继承 虚函数 内存分布
- 多态性与虚函数表——内存分布
- C++浅析——继承类内存分布和虚析构函数
- 动态分布内存——free()函数与realloc()函数
- 如何理解java继承中内存分布
- 【c++】深度探索虚继承内存布局
- c++内存分布(二)--虚函数和虚继承
- 转载:虚函数和虚继承的内存分布
- C++内存分布之菱形继承(无虚函数)
- c++11 学习及测试(虚继承 虚函数时类的内存分布)
- c++虚函数实现机制以及类继承中的内存分布
- C++类内存分布(带有虚继承)①---补充(11)《Effective C++》
- C++内存分布之单继承和多继承(无虚函数)
- 【c++】深入剖析虚拟继承与各种继承关系中派生类内成员内存分布情况及虚基类表的内容
- 虚继承下对象的内存分布
- (二十五)进程——wait与waitpid、僵尸进程与孤儿进程
- LintCode 87-删除二叉查找树的节点
- 函数参数相关扩展
- 宏定义函数
- 如何用消息系统避免分布式事务?
- C++类内存分布——深度理解继承与虚函数
- Linux下用samba共享文件
- 前端新人整理各种三角问题
- laravel路由、中间件、控制器等简单笔记
- 作业:8.15 最大公共子图
- MySQL字符集转化过程
- 21. Merge Two Sorted Lists*
- STM32学习笔记8——串口输出数据丢失问题
- 2017个人规划