结构体,枚举,联合大小的计算规则

简介: 结构体,枚举,联合大小的计算规则

1.结构体大小的计算

(1)结构体内存对齐的规则


1. 第一个成员在与结构体变量偏移量为 0 的地址处。



2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的对齐数 与 该成员大小的 两者之间的较小值 。

VS 中默认的值为 8     gcc没有默认对齐数,那么对齐数就是该成员的大小



3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。



4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


例如


struct S1
{
  char c1;//大小为1,默认对齐数为8,取其小就是1,所以其对齐数是1
  int a;//大小为4,默认对齐数为8,取其小为4,所以其对齐数就是4
  char c2;
}
int main()
{
  struct S1 s1={0};
  printf("%d\n",sizeof(s1));
}

其内存分配如图所示



其中的成员占了9个内存单元,但是结构体的内存大小是其最大对齐数的整数倍,这里的最大对齐数是4,所以在9以上的,最小的,4的倍数是12


再来一例,与上面同理,直接看图


struct S2
{
  char c1;
  char c2;
  int a;
}
int main()
{
  struct S2 s2={0};
  printf("%d\n",sizeof(s2));
}

成员的内存总共为8个内存单元,刚好也是4的倍数


可以看到两个结构体的成员是相同的,但是后者占用内存比前者少,所以总结起来


将占用内存小的成员写在前面 ,这样能更加高效的利用空间


(2) 那么既然有空间的浪费,为什么不紧密的存放每一个成员呢,内存对齐的意义


1. 平台原因 ( 移植原因 ) :

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

2. 性能原因 :

数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总体来说: 结构体的内存对齐是拿空间 来换取 时间 的做法。


(3)修改默认对齐数

结构在对齐方式不合适的时候,可以自己更改默认对齐数。


#pragma 这个预处理指令,可以改变默认对齐数,例如


#pragma pack(1)设置默认对齐数为1,这样每一个结构体成员都是紧密排列的了


#pragma pack(1)//设置默认对齐数为1
struct S2
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
补充(位段)
位段的内存分配
1.位段的成员可以是intunsignedint signedint或者是char(属于整形家族)类型
2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
struct S
{
  int a;
  int b;
  int c;
  int d;
};
按照上面的结构体的计算规则,那么这个结构体的大小为16
对于位段是8个字节
struct S
{
  int a:2;//冒号后的数字表示a只需要2个比特位
  int b:5;
  int c:10;
  int d:30;
}
//总共需要47个比特位,那么6个字节足够(6*8)=48,计算的结果却为8个字节
 注意位段是按整型大小的字节进行开辟的



注意:


struct S
{
  int a:2;
  int b:5;
  int c:10;
  int d:33;//不能超过32个比特,报错:d的位域大小无效
}

本来int类型需要占用32位空间,现在只需要占用2个比特位的空间,位段的使用节省了空间大小


举例

struct S
{
  char a:3;
  char b:4;
  char c:5;
  char d:4;
}
int main()
{
  struct S s={0};
  s.a=10;//二进制序列1010
  s.b=20;
  s.c=3;
  s.d=4;
  return 0;
}


首先struct S s={0};所以先给s分配8个比特位





如上图所示a只有3个比特位,但是s.a=10//二进制序列为1010,所以内存中只能存3个比特位,即1010中的010


s.b=20//二进制序列10100占5个比特,但是b只有4个比特,所以只能传“0100”


s.c=3//二进制序列011占3个比特,但是c有5个比特,前面补0,即“00011”


s.d=4//二进制序列为100,但是d占4个比特,前面补0,即“0100”


所以总体写下来如图所示


阅读内存时,内存是16进制数字,则四个2进制位为一个16进制,从左往右依次读


0010 0010 0000 0011 0000 0100


2       2       0       3       0       4  


位段的跨平台问题


1. int 位段被当成有符号数还是无符号数是不确定的。

2. 位段中最大位的数目不能确定。

( 16 位机器最大 16 , 32 位机器最大 32 ,写成 27 ,在 16 位机器会出问题。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的


总结


跟结构相比,位段可以达到同样的效果,可以很好的节省空间,但是有跨平台的问题存在


应用:用位段封装网络上传输的数据包等


2.枚举的大小(4个字节)

enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5;
enum Sex
{
  MALE,
  FEMALE,
  SECRET
}
int main()
{
  enum Sex s=MALE;
  printf("%d\n",sizeof(s));
  return 0; 
} 
结果为4


3.联合大小的计算

(1)计算规则


联合的大小至少是最大成员的大小。


当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍


union Un
{
  int a;//成员大小为4,默认对齐数为8,对齐数为4
  char arr[5]; //成员大小char是1,默认对齐数是8,得到对齐数是1,不要拿数组的大小进行计算
}
int main()
{
  union Un u;
  printf("%d\n",sizeof(u));
  return 0;
}

如果联合体如下图所示



那么其占用内存总大小为5个内存单元,但是联合的大小


至少是最大成员的大小。


当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍


5(最大成员大小)不是(最大对齐数)4的整数倍,所以应该按下图进行内存分配


如图浪费了3个内存单元,使得总内存为8个字节,为最大字节数(4)的整数倍  

目录
相关文章
|
弹性计算 JavaScript 前端开发
常见类型-1:空,联合,枚举
本实验将介绍TypeScript中空,联合,枚举类型的用法。
|
2月前
|
JavaScript
通过类型缩小来处理联合类型值
通过类型缩小来处理联合类型值
15 0
|
4月前
|
存储 编译器 Linux
自定义数据类型:结构体+枚举+联合
自定义数据类型:结构体+枚举+联合
|
4月前
|
JavaScript 前端开发 编译器
TypeScript中的高级类型:联合类型、交叉类型与条件类型深入解析
【4月更文挑战第23天】探索TypeScript的高级类型。这些特性增强类型系统的灵活性,提升代码质量和维护性。
|
存储
详解位段+枚举+联合(接结构体)(一)
详解位段+枚举+联合(接结构体)
51 0
|
存储 编译器 C语言
自定义数据类型:结构体,枚举,联合
自定义数据类型:结构体,枚举,联合
|
存储
学C的第三十天【自定义类型:结构体、枚举、联合】-2
(7). 修改默认对齐数: 结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。 使用 #pragma 预处理指令,修改默认对齐数
|
10月前
结构体、枚举、联合详解(下)
结构体、枚举、联合详解(下)
33 0
|
10月前
|
编译器 C语言 C++
结构体、枚举、联合详解(上)
结构体、枚举、联合详解(上)
28 0
|
存储 C#
C#基础⑥.1——枚举、结构体
枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。也就是一些固定范围的值。