结构体内存对齐,位段,枚举相关问题.

本文标签:    结构体内存对齐 结构位段问题 枚举

一、结构体的内存对齐.

1.计算结构体大小

知道了结构体的基本使用,我们可以深入探讨一个问题:计算结构体的大小.

这也是一个热门的考点:结构体内存对齐.

示例代码:

 按照我们以往的理解, char 占一个字节, int 占四个字节, 应该是 6 个字节的大小,但是编译得到的结果是 12 个字节.由此我么引入了 结构体的内存对齐知识点.

结构体的成员在内存中存储是有自己的规则的,规则如下:

1.第一个成员在与结构体变量偏移量为 0 的地址处;
2.其他成员变量要对齐到 对齐数 的整数倍的地址处;


对齐数 = 编译器默认的一个对齐数 与 该成员大小的 较小值.( vs 默认的值为 8 .)


3.结构体总大小为对齐数中最大的那个对齐数 ( 每个成员变量都有一个对齐数 ) 的整数倍;
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处 , 结构体的整体大小就是所有最大对齐数( 含嵌套结构体的对齐数 )的整数倍.

  1. 第一句:示例代码中第一个成员 c1 .它相对于起始位置的偏移量就是 0.(也就是结构体的第一个成员就是从 0 偏移量的地址处开始存放.)此时 c1 占 1 个字节.

2.第二句: vs 平台默认对齐数是 8,也就是用 8 和当前的 i (整型 4 个字节)比较,取较小值就是 4 .然后根据对齐规则,要将第二个变量的地址存放到对齐数 4 的整数倍的地址处,4 的整数倍地址就是 4 ;

而下一个变量 c2 占 1 个字节大小,将它与对齐数 8 比较,取较小值 1 也就是 c2 的对齐数,将 c2 从 1的整数倍处存放一个字节的内存.所以当前 c1,c2 i 一共占 9 个字节的空间

 3.第三句:要算最终结构体的大小,就要算出每个结构体成员变量中的最大对齐数的整数倍. c1,i,c2的对齐数分别是 1,4,1 ,最大对齐数就是 4 ,所以结构体的总大小就要是 4 的整数倍,而当前结构体大小是 9 ,要使它是 4 的整数倍,就要再浪费掉 3 个字节的空间,最终就会得到结构体总大小就是 12 .

 

 例题:计算一下结构体的大小

struct s1
{
	char c1;
	int i;
	double d;
};

首先 c1 存放在 0 偏移量处, 然后 i 的对齐数是 4 ,所以要存放到 4 的整数倍处,占 4 个字节,然后变量 d 的对齐数是 8 ,当前地址也是 8 ,所以向下存放 8 的字节的空间.现在一共占 16 个字节的空间, 16 是所有变量的最大对齐数 8  的倍数,所有这个结构体大小就是 16 . 

 4.第四句:可以结合以下代码理解.

根据第四个规则,首先将 c1 存放到 0 偏移量处,第二个结构体成员也是一个结构体,它的大小刚刚计算过了是 16 ,它如果要开始存储,就要对齐到它本身结构体成员中最大对齐数的整数倍处,它的成员最大对齐数是 8 ,所有要从 8 的整数倍地址处开始存放 16 个字节;

现在的偏移量是 24 它是 d 对齐数 8 的整数倍,再向下存放 8 个字节的空间.现在整体的总大小是 32 ,根据规则,它当前的总大小要是所有变量(包括嵌套的结构体成员变量)的最大对齐数的整数倍,如果不是则要继续浪费空间,而当前嵌套结构体的最大对齐数是 8 ,32 是 8 的整数倍,所有这个嵌套结构体的总大小就是 32 .

 

2.为什么存在内存对齐?

为什么存在内存对齐?
1.平台原因 : 不是所有的硬件平台都能访问任意地址上的任意数据的 , 某些硬件平台只能在某些地址处取特定的类型数据 , 否则抛出硬件异常.

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


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

二、结构体的位段问题.

位段的声明和结构是类似的 , 有两个不同:

  1. 位段的成员必须是:int,unsigned或signed int.
  2. 位段的成员名后面有一个冒号和一个数字.

示例代码:

struct S
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};

只要在结构体成员变量的后面加上冒号和一个数字就是位段.

代码的意思是 a 成员占 2 个 bit 位,b 占 5 个 bit 位, c 占 10 个 bit 位, d 占 30 个 bit 位.那位段成员是怎么分配空间的呢?由此引入以下概念:

位段的内存分配:


1 . 位段的成员可以是int, unsigned int ,signed int或者是char(属于整型家族)类型.
2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的.
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段  .

也就是说如果位段中的类型是 int ,它就是一个整型一个整型的来开辟空间,首先 a 开辟一个 int 的空间也就是 4 个字节,开辟 32 个 bit 的空间,而位段定义 a 只占 2bit 的空间,还剩下 30 个 bit ,而 b 的 5 个 bit 也会存放到 a 剩下的 30 个 bit 中,就还剩下 25 个 bit 位,接着 c 占 10 个 bit ,还剩 15 bit, 而 d 需要 30 个 bit ,而剩下的字节不够存放 d ,就会再开辟一个 4 字节的空间.所有就可以求出这个位段的大小是 8. 

 在上面的代码中, c 成员用完 10bit 的后还剩下的 15 bit 不够存放 d 的 30bit ,所以要再开辟 4 个字节.但是那剩下的 15 bit是 d 用了再开辟 4 字节再用 15bit ,还是不用剩下的 15bit 只在新开辟的 4字节中存放?

这些因素在C语言中是不确定的,是不跨平台的,在不同的平台上可能实现是不一样的,所以要使用位段要先研究当前平台的规定,写出针对不同平台的代码.

位段的跨平台问题:
1.int位段被当成有符号数还是无符号数是不确定的.
2.位段中最大位的数目不能确定(16位机器最大16,32机器最大32).
3.位段中的成员在内存中从左向右分配,还是从右向左标准未定义.
4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃位重新开辟还是利用,也是不确定的.

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

三、枚举

1.枚举类型定义

枚举:

 

1.虽然枚举类型定义和结构体的类型定义很相似,但是他们俩是不一样的.

结构体:

2. 既然知道枚举类型是常量,那常量必然是有值的,我们可以将它的值打印出来.

打印枚举常量的值:

 结论:这些常量的值是依次递增 1 的方式往下走.(默认是增长1).

3.既然是常量但是我们不能对它进行赋值,假设对枚举类型的常量赋值一个 int ,编译器就会报错.

因为枚举是一个类型,我们不能将类型赋值为另一个类型.

 4.如果在枚举类型中给它的常量赋值(这里是在给它枚举类型中的常量赋值),它的下一个常量的值仍然是在当前常量的值的基础上递增 1.

2.枚举的优点

为什么使用枚举?

我们可以使用 #define 定义常量,为什么非要使用枚举? 枚举的优点:

1.增加代码的可读性和可维护性.
2.和#define定义的标识符比较枚举有类型检查,更加严谨.
3.防止命名污染.
4.便于调试.
5,使用方便,一次可以定义多个常量.


总结

  • 结构体内存对齐
  • 位段问题
  • 枚举

抽空水的文章,看到这了给个三连呗.如有不足,还望指出.

猜你喜欢

转载自blog.csdn.net/2201_75533641/article/details/129649728