结构体类型(一般方式和位域存储方式)的sizeof

结构体在内存中的存放是按照字节对齐来实现存放的

什么是对齐,以及为什么要对齐: 
    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。 
对齐的作用和原因:

    各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况, 但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为 32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。

 

1)一般结构体 

结构体存储遵循下述两点规则:

  1. 整体空间是结构体内占用空间最大的成员的宽度的整数倍
  2. 数据对齐原则---内存按结构体内成员的先后顺序排列,每一个成员摆放的初始位置都必须是该成员类型所占内存宽度的整数倍,如果不够就向后偏移,直到补齐到最近的整数倍位置。(内存存储位置是从0开始算的,int从0,4,8这样的位置算起,char任何位置OK,double从0,8,16等这样的位置算起)

ex:

struct S1
{
int i;   //(4个字节)
char c;   //(1个字节)
double d;   //(8个字节)
};  //16个字节  

int从4的整数倍位置开始存放,所以第一个位置0合适,char从1的整数倍开始存放,需一个字节,所以存在第五位上合适,double需要在8的整数倍位置上存储,而距离5最近的就是8号位置,所以char之后偏移3个字节再存放double,所以总共需要的内存数为4+1+3+8=16字节,double最宽共8字节,16为8的两倍,符合规则1。int从0开始,char从5开始,double从8开始,符合规则2。

struct S2
{
int i;   //(4个字节)
double d;   //(8个字节)
char c;   //(1个字节)
};    //24个字节   

int从4的整数倍位置开始存放,所以第一个位置0合适,double需要在8的整数倍位置上存储,而距离4最近的就是8号位置,所以int之后偏移4个字节再存放double,那么char起始位置为16,符合1的整数倍,这时所有成员排下来共占用4+4+8+1=17个字节位置,不是max(4,8,1)=8的整数倍,所以在后续需要空出7个字节的位置来填补,所以总共需要的内存数为4+4+8+1+7=24字节,double最宽共8字节,24为8的两倍,符合规则1。int从0开始,double从8开始,char从16开始,符合规则2。


2) 对于以位域方式存储的结构体:

      注:位域成员不能单独被取sizeof值,我们这里要讨论的是含有位域的结构体的sizeof。成员的位域值不能超过其类型的字节宽度,否则可能要报错,比如int型的,其位域在范围[1,32]中,short在[1,16]中,char都可以,不是所有的类型都能够用位域表示。

规则:

  1. 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
  2. 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
  3. 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
  4. 如果位域字段之间穿插着非位域字段,则不进行压缩;
  5. 整个结构体的总大小为最宽基本类型成员大小的整数倍。  
ex:

struct S3
{
short a:1;   //(1bit)
char b:7;   //(7bit)
short d:1;   //(1bit)
};    //6个字节    

首先,short是2个字节,从2的整数倍开始存放,那么0位置是合适的,然后是char类型,和short是不同的类型,这是在VC6中,所以根据第三条,采取不压缩方式,那么short仍然是要占用2个字节宽度(位域为1,然后补齐15个位域位置,凑够2个字节=2*8位),char从第2个字节处开始存储,需要7bit的位置,小于一个字节,但是第三个为short,其应该从0,2,4这样的位置开始存放,所以在char之后要补齐到4字节的位置,即补9个bit位置,那么这样下来共需存储空间为1bit+15bit+7bit+9bit+1bit=33bit,但是根据5,整体大小应该为最宽基本类型(short)的整数倍,所以应该是2字节的整数倍,那么离33bit最近的整数倍字节为6字节=48bit,所以补齐到48bit位置,故而整个结构体总的存储空间为6字节=48bit

struct S4
{
int a:1;   //(1bit)
int b:7;   //(7bit)
int d:1;   //(1bit)
};    //4个字节    
首先,int是4个字节,所以a应从4的倍数开始存放,0符合条件,第二个仍为整形,和第一个一样,所以可以采取压缩的方式,直接放在第1个bit位置上,第三个仍然为int,可以放在第(1+7-1)个位置上,但由于int的宽度为4个字节,所以应该要补全到4个字节位置上,这时整的存储空间是4个字节。

struct S5
{
int a:1;   //(1bit)
int b;   //(4个字节)
int d:1;   //(1bit)
};    //12个字节    

中间穿插非位域形式的时候,就不压缩,第一个1个字节,是从0位置开始,合适,第二个就应该从4的整数倍位置开始,而不是连在第一个位置后面,所以第一个后面应填补31个bit,第三个的话放在8字节位置处,是4的整数倍,这样总体是65个bit,不是4字节的整数倍,所以最后补充31个bit,这样总体就是12个字节了。


要返回C++中的位域(bitfield)结构体,您可以按照以下步骤操作: ### 步骤一:定义位域结构体 首先,在您的程序中定义一个包含位域成员的结构体。例如: ```cpp #include <iostream> struct BitFieldStruct { int a : 5; // 定义了一个占5位的int类型位域a int b : 3; // 定义了一个占3位的int类型位域b // 您还可以定义其他非位域成员以增强功能 }; ``` ### 步骤二:初始化并使用位域结构体 接着,初始化这个结构体并将数据赋给其成员变量。这可以通过直接赋值或通过`memcpy()`函数完成。 ```cpp BitFieldStruct obj; char str[] = "hello world"; memcpy(&obj, str, sizeof(BitFieldStruct)); std::cout << "Value of obj.a: " << static_cast<int>(obj.a) << std::endl; std::cout << "Value of obj.b: " << static_cast<int>(obj.b) << std::endl; ``` 这里的关键点是使用`static_cast<int>`将位域转换成`int`类型以便于输出。这是因为位域可能只占用部分位,而`int`类型在大多数情况下占据更大的内存空间。 ### 相关问题: 1. **如何在位域中实现更复杂的数据存储?** 如何结合位域与其他数据类型? 可以通过定义混合结构体来实现,其中包含位域与普通字段。例如: ```cpp struct ComplexStructure { int a : 5; // 位域a double c; // 浮点数c // 构造函数或其他成员函数可用来初始化这些字段 }; ``` 2. **如何安全地访问和修改位域?** 访问位域时应避免意外修改到相邻的内存位置。通常,编译器会自动处理对位域边界的安全访问,但当手动操作低级内存时需特别注意。 3. **如何利用位域优化内存使用?** 位域可以用于节省内存空间,特别是当您需要存储一组小范围值的集合时。它们适合表示具有固定位数的开关状态、标识符或配置选项。然而,在某些情况下,如数据类型之间存在较大差异时,位域可能导致额外的计算开销,因为它们可能不充分利用字节对齐特性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值