C++ 类大小分析

以下测试代码的运行环境:

Ubuntu 16.04.4 LTS

gcc version 4.8.5

x64

  • 空类、单一继承的空类、多重继承的空类所占空间大小为:1(字节)。

       实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址

  • 一个类中,虚函数本身、成员函数(包括静态与非静态)和静态数据成员都是不占用类对象的存储空间。

      类中如果有虚函数,那么该类对象会生成一张虚函数表,然后编译器会生成一个指向该虚表的指针vptr(virtual table pointer),该指针占用存储空间,具体下面虚函数会讲解,静态数据成员存放的在静态存储区,跟类对象存储空间不一样。

  • 为了优化存取效率,通常都会遵守内存对齐原则。

     通俗的理解是,类中最长的数据成员作为对齐原则,从其内部"最宽基本类型成员"的整数倍地址开始存储。比如类中有 char、int、float、double等元素,那么类应该从8的整数倍开始存储。

例如:

class AA{

public:

    char i;

    char j;

    double k;

};

int main()
{
    cout<<"size = "<<sizeof(AA)<<endl;
    return 0;
}

输出:16

该类大小等于数据大小之和,由于该类的“最宽”数据类型为double,所以按照8的整数倍进行存储,这里的计算方式:1+1+(6)+8 = 16.

注释:上面计算方法中的“(6)”是内存补齐,编译器自动处理的。之后的计算类似这样都表示内存补齐。

扫描二维码关注公众号,回复: 4425605 查看本文章

注意,如果数据的摆放顺序不一样,则类的大小也是有区别的:

class AA{

public:

    char i;

    double j;

    char k;

};

int main()
{
    cout<<"size = "<<sizeof(AA)<<endl;
    return 0;
}

输出:24

类中的数据成员是按照顺序进行摆放的,所以计算方式:1+(7)+8+1+(7) = 24

  • 虚函数,有虚函数的类有个virtual table(虚函数表),里面包含了类的所有虚函数,类中有个virtual table pointers,通常成为vptr指向这个virtual table。

一个类里边无论一个或者多个虚函数,都只有一个vptr,只要有一个虚函数,类中肯定会生成一张虚函数表vtable,表中保存的就是每个虚函数的地址。vptr指向vtable,然后在vtable中找到对应的虚函数。

class AA{
public:
    char i;
    int j;
    int k;
    virtual void func();
    virtual ~AA();
};

输出:24

注意,这里也有一个坑,由于测试环境是gcc version 4.8.5,ubuntu 系统 64位,所以虚函数指针vptr是8个字节,如果系统是32位,则是4个字节。这里的计算方式:1+4+(3)+4+(4)+8 = 24.

  • 64位和32位,数据类型大小的区别

最大的区别就是指针数据类型64位的大小是8个字节,32位的是4个字节

具体请参考:https://blog.csdn.net/puppet_master/article/details/50044965

  • 带有虚函数的单继承

子类继承一个带有虚函数的基类,无论子类有没有虚函数,或者有虚函数覆盖的情况,子类也只有一个虚函数表。

继承关系图如下:参考https://www.cnblogs.com/yanqi0124/p/3829964.html

基类和派生类之间的关系:其中基类的虚函数f在派生类中被覆盖了

class AA{
public:
    char i;
    int j;
    int k;
    virtual void func();
    virtual ~AA();
};

class BB : public AA{
public:
    int n;
    virtual void func();
};

 

输出: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。

class AA{
public:
    int i;
    virtual void funcd();
    virtual ~AA();
};

class BB{
public:
    int j;
    virtual void func();
    virtual ~BB();
};

class CC : public AA, public BB{
public:
    int k;
    virtual void func();
    ~CC();
};

输出:

AA大小:16

BB大小:16

CC大小:32

  • 虚继承,由于涉及到虚函数表和虚基表,会同时增加一个(多重虚继承下对应多个)vPtr指针指向虚函数表vbTable和一个vbPtr指针指向虚基表vbTable。

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编译器上,则虚函数指针不共享。

猜你喜欢

转载自blog.csdn.net/yj_android_develop/article/details/82015207