以下测试代码的运行环境:
Ubuntu 16.04.4 LTS
gcc version 4.8.5
x64
实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址
类中如果有虚函数,那么该类对象会生成一张虚函数表,然后编译器会生成一个指向该虚表的指针vptr(virtual table pointer),该指针占用存储空间,具体下面虚函数会讲解,静态数据成员存放的在静态存储区,跟类对象存储空间不一样。
通俗的理解是,类中最长的数据成员作为对齐原则,从其内部"最宽基本类型成员"的整数倍地址开始存储。比如类中有 char、int、float、double等元素,那么类应该从8的整数倍开始存储。
例如:
输出:16
该类大小等于数据大小之和,由于该类的“最宽”数据类型为double,所以按照8的整数倍进行存储,这里的计算方式:1+1+(6)+8 = 16.
注释:上面计算方法中的“(6)”是内存补齐,编译器自动处理的。之后的计算类似这样都表示内存补齐。
注意,如果数据的摆放顺序不一样,则类的大小也是有区别的:
输出:24
类中的数据成员是按照顺序进行摆放的,所以计算方式:1+(7)+8+1+(7) = 24
一个类里边无论一个或者多个虚函数,都只有一个vptr,只要有一个虚函数,类中肯定会生成一张虚函数表vtable,表中保存的就是每个虚函数的地址。vptr指向vtable,然后在vtable中找到对应的虚函数。
输出:24
注意,这里也有一个坑,由于测试环境是gcc version 4.8.5,ubuntu 系统 64位,所以虚函数指针vptr是8个字节,如果系统是32位,则是4个字节。这里的计算方式:1+4+(3)+4+(4)+8 = 24.
最大的区别就是指针数据类型64位的大小是8个字节,32位的是4个字节
具体请参考:https://blog.csdn.net/puppet_master/article/details/50044965
子类继承一个带有虚函数的基类,无论子类有没有虚函数,或者有虚函数覆盖的情况,子类也只有一个虚函数表。
继承关系图如下:参考https://www.cnblogs.com/yanqi0124/p/3829964.html
基类和派生类之间的关系:其中基类的虚函数f在派生类中被覆盖了
输出:AA大小:24,BB大小:24
BB继承AA的所有数据,其数据成员存放顺序如下:
char i;
int j;
int k;
int n;
计算方法:1+4+(3)+4+4+8(vptr大小) = 24
继承关系图如下:参考https://www.cnblogs.com/yanqi0124/p/3829964.html
假设基类和派生类之间有如下关系:
对于子类实例中的虚函数表,是下面这个样子:
假设,基类和派生类又如下关系:派生类中覆盖了基类的虚函数f
下面是对于子类实例中的虚函数表的图:
从上图中可以看出,只要继承的父类有虚函数,那么子类就有一个对应的指向基类虚表的指针vptr。
输出:
AA大小:16
BB大小:16
CC大小:32
class AA{
public:
int i;
virtual void funcaa();
virtual ~AA();
};
class BB : virtual public AA{
public:
int j;
virtual void funcbb();
virtual ~BB();
};
class CC : virtual public AA{
public:
int k;
virtual void funccc();
~CC();
};
class DD : public BB, public CC{
virtual void funcdd();
~DD();
};
输出:
AA大小:16
BB大小:32
CC大小:32
DD大小:48
虚继承根据编译器的不同,对象内存布局也不一样,再次强调,本文章里边所有测试环境是GCC编译器。GCC都是将虚表指针在整个继承关系中共享的,不共享的是指向虚基类的指针。
对于BB对象内存分析:
AA::vptr | //虚表指针 |
AA::i | |
BB::vbptr | //虚基类指针 |
BB::vptr | //虚表指针 |
BB::j |
由于虚函数指针共享,所以BB最终的大小计算:8(AA::vbptr)+(4+(4))(AA::i)+(4+(4))(BB::j)+8(vptr 共享虚函数指针)=32
CC跟BB是类似的计算。
DD的对象内存分析:
BB::vptr | //虚表指针 |
BB::vbptr | //虚基类指针 |
BB::j | |
CC::vptr | //虚表指针 |
CC::vbptr | //虚基类指针 |
CC::k | |
AA::vptr | //虚表指针 |
AA::i |
DD大小计算:8(BB::vbptr)+(4+(4))(BB::j)+8(CC::vbptr)+(4+(4))(CC::k)+(4+(4))(AA::i)+8(vptr 共享虚函数指针)=48
补充:如果是在VC编译器上,则虚函数指针不共享。