C++ sizeof不完全总结

本文详细解析C++中sizeof运算符的工作原理及应用,包括基本类型、数组、指针、复合结构(如结构体、联合体)、类等不同场景下的计算规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文是我在复习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
}

运行结果如下:



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




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值