C++ sizeof不完全总结

来源:互联网 发布:虚拟网络传销中的三虚 编辑:程序博客网 时间:2024/04/30 13:03

本文是我在复习c++时,遇到各种计算sizeof操作的不完全总结,由于个人水平有限,不一定保证所说的完全正确,欢迎大家交流。


sizeof 是C/C++的一个操作符,它的作用是返回一个对象或类型所占的内存的字节数。

MSDN上的解释为:

  The sizeof keyword gives the amount ofstorage, in bytes, associated with avariable or a type(including aggregatetypes). This keyword returns a value of type size_t.

 

其返回值类型为size_t,在头文件stddef.h中定义。这是一个依赖于编译系统的值,一般定义为

typedef unsigned int size_t;

sizeof有三种语法形式,如下:

sizeof(object);//sizeof(对象);

sizeof(type_name);//sizeof(类型);

sizeofobject;//sizeof对象;


1.    作用于基本类型时

sizeof作用于基本类型时,基本类型变量所占的内存大小和基本类型所占的大小一样,如下面代码所示。

#include <iostream>using namespace std;int main(){    char a;    bool b;    short c;    int d;    float f;    double db;    cout<<"sizeof(a): "<<sizeof(a);    cout<<" , sizeof(char) :"<<sizeof(char)<<endl;    cout<<"sizeof(b): "<<sizeof(b);    cout<<" , sizeof(bool) :"<<sizeof(bool)<<endl;    cout<<"sizeof(c): "<<sizeof(c);    cout<<" , sizeof(short) :"<<sizeof(short)<<endl;    cout<<"sizeof(d): "<<sizeof(d);    cout<<" , sizeof(int) :"<<sizeof(int)<<endl;    cout<<"sizeof(f): "<<sizeof(f);    cout<<" , sizeof(float) :"<<sizeof(float)<<endl;    cout<<"sizeof(db): "<<sizeof(db);    cout<<" , sizeof(double) :"<<sizeof(double)<<endl;    }

运行结果如下:


2.    当用于数组,指针时

sizeof用于数组时,数组所占的内存为数组中每个元素所占的内存 *数组中元素的数目。当数组作为函数形参时,又有所不同。

#include <iostream>using namespace std;int main(){    char arr[10];    int aa[10];    double db[10];    cout<<"sizeof(char arr[10]) : "<<sizeof(arr)<<endl; //10    cout<<"sizeof(int aa[10]) : "<<sizeof(aa)<<endl;   //40    cout<<"sizeof(double db[10]): "<<sizeof(db)<<endl; //80}

运行结果如下:



sizeof运用指针p时,与作用于数组有些不同。指针是用来存储变量的地址的,在不同位的系统中,地址的寻址空间不一样,导致表示一个地址所需的位数也不同。

在32位系统中,一个地址占32位,即4个字节,而64位系统中需要8个字节。所以在32位系统中,指针所占的内存空间为4个字节。以下实验是在32位系统下完成的。


#include <iostream>using namespace std;int main(){    char ca;    char carr[6];    char *pc = &ca;    char *pcc = carr;    cout<<"sizeof(pc) : "<<sizeof(pc)<<endl;     //4, not 1    cout<<"sizeof(pcc) : "<<sizeof(pcc)<<endl;   //4, not 6    cout<<"sizeof(*pc) : "<<sizeof(*pc)<<endl;   //1    cout<<"sizeof(*pcc) : "<<sizeof(*pcc)<<endl; //1    cout<<"sizeof(\"hello\") : "<<sizeof("hello")<<endl; //6, include '\0'    int ia;    int iarr[5];    int *pi = &ia;    int *pii = iarr;    cout<<"sizeof(pi) : "<<sizeof(pi)<<endl;  //4    cout<<"sizeof(pii) : "<<sizeof(pii)<<endl; //4    cout<<"sizeof(*pi) : "<<sizeof(*pi)<<endl; //4    cout<<"sizeof(*pii) : "<<sizeof(*pii)<<endl; //4    double da;    double drr[2];    double *pd = &da;    double *pdd = drr;    cout<<"sizeof(pd) : "<<sizeof(pd)<<endl;   //4    cout<<"sizeof(pdd) : "<<sizeof(pdd)<<endl;  //4    cout<<"sizeof(*pd) : "<<sizeof(*pd)<<endl;  //8    cout<<"sizeof(*pdd) : "<<sizeof(*pdd)<<endl;  //8}

运行结果如下:



当数组作为函数形参时,传递过去的并不是数组本身,而是数组的地址,这在计算sizeof时,发生了一些令人诧异的现象。

#include <iostream>using namespace std;int cal(char arr[]){    return sizeof(arr);}int main(){    char arr[10];    cout<<sizeof(arr)<<endl; //10    cout<<cal(arr)<<endl;//4, not 10}

运行结果如下:


从结果可以看出,在cal函数中,传递的参数实际上是数组的地址。

3. Sizeof用于复合结构


Sizeof用于string

string并没有规定实施的标准,和编译器有关,它输出的是string对象或类所占的内存大小,不同编译器的结果不一样,有的返回4,16,32


#include <iostream>#include <string>using namespace std;int main(){    string str;    cout<<"sizeof(str) : "<<sizeof(str)<<endl;              //32, @vs2010    string str2 = "hello world!";    cout<<"sizeof(str2) : "<<sizeof(str2)<<endl;            //32    string str3 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";    cout<<"sizeof(str3) : "<<sizeof(str3)<<endl;            //32    cout<<"sizeof(str3.c_str()) : "<<sizeof(str3.c_str())<<endl;  //4}

运行结果如下:



Sizeof用于结构体

Sizeof用于结构体时,返回值为结构体中每一个成员变量所占的内存之和,如果一个结构体为空,那么它所占的内存为1个字节。但需要注意的是,在结构体中,需要注意内存对齐的问题。

 

一、内存对齐的原因

大部分的参考资料都是如是说的:

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。


二、对齐规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n)n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

 

对齐步骤:

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

3、结合12颗推断:当#pragma packn值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

备注:数组成员按长度按数组类型长度计算,如char t[9],在第1步中数据自身长度按1算,累加结构体时长度为9;2步中,找最大数据长度时,如果结构体T有复杂类型成员A的,该A成员的长度为该复杂类型成员A的最大成员长度。

 

当不使用#pragmapack

#include <iostream>#include <string>using namespace std;struct A{};struct B{    char c;  //1,char以1为对齐系数,所以 offset[0,3],包括填充字节    int a;   //4,int以4为对齐系数,所以 offset[4,7]    short b; //2,short以2为对齐系数,所以 offset[8,15]    double d; //8, double 以8为对齐系数,所以offset[16,23]    //结构整体以double的大小(8个字节)为对齐依据,所以b占8个字节    //其中2个是自身数据,后面6个字节是填充的    //整个size为 8+8+8 = 24 字节    //如果没有d,则整体按int的大小对齐,共占(4+4+4)=12字节};struct C{    char c;  //1, char以1为对齐系数,所以offset[0,1]    short b; //2, short以2为对齐系数,所以offset[2,3]    int a;   //4, int以4为对齐系数,所以offset[4,7]    double d; //8, double以8无对齐系数,所以offset[8,15]    //整体的size为 8(c,b,a)+8(d) = 16字节    //如果没有d,则整体按int的大小对齐,共占(2+2+4)=8字节  };struct D{    char arr[6];  //虽然是6个字符的数组,但以char大小对齐    char temp;     //整个大小为 6+1 = 7 字节};int main(){    cout<<sizeof(A)<<endl; //1    cout<<sizeof(B)<<endl; //24    cout<<sizeof(C)<<endl; //16    cout<<sizeof(D)<<endl; //7}

运行结果如下:



BC的对比中,我们可以发现,同样的数据,对齐方式不同,所占的内存大小也不一样。这对我们以后自己写程序有很大启示。

 

现在改变一下,以#pragma pack指定对齐大小

当使用#pragma pack(1)

#include <iostream>#include <string>using namespace std;#pragma pack(1)struct A{};struct B{    char c;      int a;       short b;     double d;     // 1+4+2+8 = 15字节};struct C{    char c;      short b;     int a;       double d;     // 1+2+4+8 = 15字节};struct D{    char arr[6];      char temp;     //6+1= 7字节};int main(){    cout<<sizeof(A)<<endl; // 1    cout<<sizeof(B)<<endl; //15    cout<<sizeof(C)<<endl; //15    cout<<sizeof(D)<<endl;  //7}

运行结果如下:


当使用#pragma pack(2)

#include <iostream>#include <string>using namespace std;#pragma pack(2)struct A{};struct B{    char c;  //offset[0,1]    int a;   //ofset[2,5]    short b; //ofset[6,7]    double d; //offset[8,15]    // 2+4+2+8 = 16字节};struct C{    char c;  //offset[0,1]    short b; //offset[2,3]    int a;   //offset[4,7]    double d; //offset[8,15]    // 2+2+4+8 = 16字节};struct D{    char arr[6];  //offset[0,5]    char temp;  //offset[6,6]    //6+1= 7字节 ,对齐系数为pragma参数和最大成员二者中的较小值};int main(){    cout<<sizeof(A)<<endl; // 1    cout<<sizeof(B)<<endl; //16    cout<<sizeof(C)<<endl; //16    cout<<sizeof(D)<<endl;  //7}

运行结果如下:


当使用#pragma pack(4)

#include <iostream>#include <string>using namespace std;#pragma pack(4)struct A{};struct B{    char c;  //offset[0,3]    int a;   //ofset[4,7]    short b; //ofset[8,11]    double d; //offset[12,19]    // 4+4+4+8 = 20字节};struct C{    char c;  //offset[0,1]    short b; //offset[2,3]    int a;   //offset[4,7]    double d; //offset[8,15]    // 2+2+4+8 = 16字节};struct D{    char arr[6];  //offset[0,5]    char temp;  //offset[6,6]    //6+1= 7字节 ,对齐系数为pragma参数和最大成员二者中的较小值};int main(){    cout<<sizeof(A)<<endl; // 1    cout<<sizeof(B)<<endl; //20    cout<<sizeof(C)<<endl; //16    cout<<sizeof(D)<<endl;  //7}

运行结果如下:



当结构体的成员中含有结构体时

当一个结构体A包含另一个结构体B时,计算A所占用的内存空间时,需要先将B中的每个成员展开来,然后再确定对齐系数,但在后面计算时,需要将B看成一个整体,具体例子见下面程序。

#include <iostream>#include <string>using namespace std;struct S1{    char a;  //1    int b;  //4    //size = 4+4 = 8字节};struct S2{    short sh;  //offset[0,1]    char cc;  //offset[2,3]    S1 s;     //offset[4,11]    //S2在考虑时对齐系数时,是把S1展开,然后再选择对齐系数    //所以对齐系数是 4    //但是在计算时 ,确需要将S1当成一个整体,所以    //size = 4+8 = 12字节};struct S3{    char a;    short b;    //size = 2+2 = 4字节};struct S4{    int aa;    //offset[0,3]    short bb;  //offset[4,7]    S3 s;      //offset[8,15]    double dd;  //offset[16,23]    //将S3展开考虑对齐系数,应为double比较大,所以对齐系数为8    //size = 4+4 + 8+ 8 = 24字节 };int main(){    cout<<"sizeof(S1): "<<sizeof(S1)<<endl;  //8    cout<<"sizeof(S2): "<<sizeof(S2)<<endl;  //12    cout<<"sizeof(S3): "<<sizeof(S3)<<endl;  //4    cout<<"sizeof(S4): "<<sizeof(S4)<<endl;  //24}

运行结果如下所示:



Sizeof用于union

结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个联合体的sizeof也就是每个成员sizeof的最大值。结构体的成员也可以是复合类型,这里,复合类型成员是被作为整体考虑的。

#include <iostream>#include <string>using namespace std;struct S1{    char a;  //1    short b;  //2    //size = 2+2 = 4字节};struct S2{    char arr[6];    // size = 6字节};union U{    char c; //1字节    S1 s1;  //4字节    S2 s2;  //6字节    //size = 6字节};int main(){    cout<<"sizeof(S1): "<<sizeof(S1)<<endl;  //4    cout<<"sizeof(S2): "<<sizeof(S2)<<endl;  //6    cout<<"sizeof(S3): "<<sizeof(U)<<endl;  //6}

运行结果如下:



4.Sizeof用于类时

当类和结构体只有除了访问方式之外,没有其他区别时,其计算内存方式和结构体类似,但当存在继承关系时,就有所不同了。

#include <iostream>#include <string>using namespace std;class A{private:    int a;  //offset[0,3]    short b; //offset[4,5]    char c;  //offset[6,7]};class B: public A{    double a;    int b;    char c;    //B占了24个字节,除了新的的16个字节之外,还有A的成员部分};class C{    char c;    short b;};class D:public C{    double d;    //d占16个字节,除了新增的8个字节外,还有C中的4个字节,填充到8个字节};int main(){    cout<<sizeof(A)<<endl; //8    cout<<sizeof(B)<<endl; //24    cout<<sizeof(C)<<endl; //4    cout<<sizeof(D)<<endl; //16}

运行结果如下:



当一个类中包含有static成员时,计算该类型对象所占用的内存空间时,不需要将static成员考虑进去,因为static成员是类共有的,并不是某一个对象所单独拥有的。

 

#include <iostream>#include <string>using namespace std;class A{private:    short b;    char c;    static int count;};int main(){    A aa;    cout<<sizeof(aa)<<endl; //4    cout<<sizeof(A)<<endl; //4}

运行结果如下:



当类中存在虚函数时

(C++ primer plus)编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个函数地址数组的指针。这种数组成为虚函数表。

虚函数表中存储了为类对象进行声明的虚函数的地址。基类的对象包含一个指针,该指针指向基类中所有虚函数的地址表。

派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;

如果派生类没有重新定义虚函数,该虚函数表将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的地址也将被添加到虚函数表中。

注意,无论类中包含的虚函数时1个还是10个,都只需要在对象中添加一个地址成员,只是表的大小不同而已。

 

下面这张图是 c++ primer plus 中的虚函数表示意图。



实例如下:

#include <iostream>#include <string>using namespace std;class A{private:    int a;    short b;public :    virtual void fun1();    virtual void fun2();    //基础数据占8个字节,虚函数表占4个字节    //一共12字节};class B:public A{    int d;    virtual void fun1();    virtual void fun2();    //A中数据占8个字节,新增数据占4个字节    //虚函数表占4个字节,一共 16字节};int main(){    cout<<sizeof(A)<<endl; //12    cout<<sizeof(B)<<endl; //16}

运行结果如下:



后续如果有新的内容会继续补充。




0 0