类大小的计算

来源:互联网 发布:苹果免费数据恢复软件 编辑:程序博客网 时间:2024/05/24 00:39

C++中基本对象的存储。(所占内存大小)

一个类对象的大小等于或大于所有非静态数据成员大小之和。

1 .大于是因为存在类中存在虚函数,虚函数指针占4个字节(32位机)).

2.  成员函数不占用内存.

3.  静态成员变量不占用类对象的存储空间.(静态成员变量所有的类对象共享一份,在静态区域中,并不占用类对象的空间。)

4. c++规定类的大小不为0,空类的大小为1. (当类不包含虚函数和非静态数据成员是,其对象大小为1)


 结构体类型类类型对齐原则:

         每个变量前面的偏移量一定是其类型大小的整数倍。否则自动补齐。

          结构体的长度一定是最长的数据元素的整数倍。否则自动补齐。


二、继承下的对象存储

1、虚表指针占用4个字节原则

对于一个类而言,在不存在虚函数的情况下,类的大小等于成员大小之和(按照对其原则),当存在虚拟函数时,由于要保存虚表指针,故多占用4个字节。

2、子类共享父类的虚表指针原则

     在普通继承下,子类与父类共享一个虚表(一个虚指针),子类不需要另外添加内存。(多重继承时,子类中保存每个基类的虚表指针,且子类和基类同样共享虚表指针)

3、虚基类表指针占用4字节原则

     在虚继承的情况下,继承了多个继承同一个父类的中间类的子类只保存了一个同他基类的备份,但每个中间类都需要需要保存指向基类表的指针来指向共同的基类。(除保留各自的虚表指(vptr)针外还要外加一个虚基类表指针(bptr)用于指向其父类)。



C++语言中类的继承是C++重要特性之一,暂且将类的继承分为成员变量以及成员函数两个方面。我们知道标准C++选择的方式是每个类对象存储一份自己的成员变量,那么这份成员变量的存储区是怎么布局的呢?下面分几种情况记录一些自己看法和体会(部分参考《Inside The C++ Object Model》)。

 

1,非静态成员变量的单一继承且不含virtual函数

 

C++标准并没有对继承来的基类的成员变量和派生类自身的成员变量的布局做一个顺序上的规定,而是由编译器来决定。大部分的编译器都是将基类成员变量放在前面,且变量之间遵循其声明的次序。

 

例如:class A                              class B : public class A

{                                                {

      public:                                     public:

                int       val;                               char  a2;

                char    a1;

}                                                 }

 

对于A类对象其内存布局很简单,和一个struct结构体的内存布局是相同的。32位计算机情况下,由于内存对齐的原因,其所占的内存大小应该为4 + 1 + 3 = 8字节(1.每个变量前面的偏移量一定是其类型大小的整数倍。2.结构体的长度一定是最长的数据元素的整数倍。否则自动补齐),当B以public继承方式继承A的成员变量后, B的内存布局即为A的成员变量位于其内存分布的前端,注意:此时B继承并不仅仅继承A中实际变量所占的内存字节,其为了内存对齐而补充的字节也一并继承,即B中继承的来自A的成员变量的内存字节数也为8字节,而不是4+1=5字节。然后B定义的自身的成员变量紧跟于继承而来的成员变量之后,同时也要注意内存对齐!则B的成员变量所占字节总数应该是8 + 1 +3 = 12字节。

 

至于为什么要将内存对齐而补充的空间一并继承据《Inside The C++ Object Model》一书介绍,是因为在C++中经常会有利用基类指针指向一个派生类成员的情况出现,例如 B m_b; A* PA = &m_b; 由指针的性质知道,PA所能指向的内存区的大小应该为数据类型A所占的大小,即为8字节,如果继承时未将对齐而填充的字节也一并继承,则m_b整个内存字节为4+1+1+2 = 8字节,此时用PA竟然可以访问不属于A类型的成员,即a2,这是不符合C++语义的。

注:sizeof(A)=16,sizeof(B)=24.空间大小是子类和基类中最大类型大小的整数倍。sizeof(D)=2.

struct A{                                 struct B:public A              struct C                  struct D :public C  

   char a;                                 {                                    {                                 {
double b;
                                    char a;                            char a;                      char b;
} ;                                                } ;                                  };                          };

2,非静态成员的单一继承,且基类中声明了虚函数

 

若基类中定义了虚函数,首先基类对象的内存布局中会增加对虚函数的支持,即一个指向virtual table的指针vptr成员,在运行期将使得每个对象利用此指针找到相应的virtual table。而构造函数应该支持对该指针赋正确的虚表地址。那么vptr如何布局到对象的内存区域呢?一般来说虚函数指针都被放在对象内存区域最前端!因此派生类继承了基类成员之后,其对象内存区的前端也应该是一个虚表指针变量!

若基类中没有定义虚函数,而派生类中定义了虚函数,则基类对象中不会添加虚表指针这个变量,而派生类中对象会添加虚表指针成员,且将此指针放于内存区的前端,即放在继承而来的基类成员变量之前,在此种情况下如果利用一个基类指针指向派生类的对象,利用该指针访问继承来的基类成员时需要编译器作出调整,该基类指针应该作出适当偏移,以跳过这个派生类的虚表指针。

若基类定义了虚函数,派生类也定义了虚函数(不管是否重载基类虚函数还是定义新的虚函数),由虚表的性质,派生类的虚表会由继承的基类的虚函数列表和自身虚函数列表共同构成一张虚函数表,且基类虚函数在前。因为只有一张虚函数表,因此在派生类对象中只会有一个虚表指针成员,该指针也会被放于内存分布的最前端。由于基类对象有虚表指针,此时用基类指针来指向一个派生类对象并访问继承的基类成员时,不需要编译器作出偏移调整。

3,非静态成员的多重继承且无虚函数

多重继承下,继承的各个基类成员变量的排列为:视各个基类对象内存布局为一个单元,按照多重继承的顺序,从左到右的先后顺序在内存区上先后排列!最后为派生类自己定义的成员变量!

4,非静态成员的多重继承且有虚函数

此时虚表指针的问题又出现了:

1)基类定义了虚拟函数,而派生类未定义虚拟函数。此时定义了虚拟函数的基类对象内存布局中有虚表指针成员,因此有多少个定义了虚函数的基类就会在派生类对象内存布局中有多少个虚表指针,分别指向继承来的各个基类的虚函数表!且各个虚表指针成员位于该继承来的基类对象内存单元的前端。

2)基类定义了虚拟函数。



0 0