结构体内存对齐,位段详解

在我们学习结构体时,可能会碰到几个难以理解的问题,一个是内存对齐,一个是位段。所以我想分享一下我对这两个问题的理解,来帮助大家更好的学习这两个知识点。

内存对齐

int main()
{
	struct
	{
		char i;
		char k; 
		int j;
	}s1;

	struct
	{
		char i;
		int j;
		char k;
	}s2;
	printf("%d\n", sizeof(s1));
	printf("%d\n", sizeof(s2));
	return 0;
}

上面的两个结构体,看上去是完全一样的,只有声明变量时的顺序不一样, 那么它们的大小一样吗?
在这里插入图片描述
运行后我们发现,它们两个的大小竟然不同,而且如果我们将它们每一个的大小相加起来,得到的也应该是6,不是上面的两个值,那么这是因为什么呢?
这时,就牵扯到了一个叫做内存对齐的东西。

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

对齐数 = 编译器默认的一个对齐数与该成员大小的较小值

. vs的默认值为8,linux默认值为4

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

光看文字的话很难看懂,所以下面我会画几幅图来描述一下内存对齐是如何运作的。

	struct
	{
		char i;
		char k; 
		int j;
	}s1;
`

在这里插入图片描述
因为第一个i与第二个j是相同类型,所以不存在偏移,k所占据的字节数比j大,所以偏移量为默认对齐数和K的大小的最小值,所以需要偏移到四个字节,所以总共占了八个字节的大小

	struct
	{
		char i;
		int j;
		char k;
	}s2;

在这里插入图片描述

看完上面的几个图解,我们了解到如果要合理运用空间,就应该把占用空间较小的成员尽量集中到一起。

那么,问题来了,为什么要有内存对齐呢?

在我们能百度到的大部分资料上,都是这样说的:

  1. 平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅仅需要一次。

看文字的话,如果你还不懂,那我再来画一幅图
在这里插入图片描述
对于没对齐过的,我们如果想读取这个k,因为我们每次读取四个字节,所以第一次读取的时候只读取到了k的前三个字节,第二次才能读取到k的第四个字节,然后将其进行重组,才能得到这个k。如果是对齐过的,我们就可以一次性读取,虽然使用的空间变多了,但是速度也快了很多。

所以内存对齐的意义就是用空间来换取时间,而空间的价格较为低廉,所以内存对齐的性价比是十分高的。

对齐数的修改

当然,系统默认的对齐数是不能适用于所有情况下的,所以我们可以修改对齐数来适用于我们所处的情景。

我们可以适用一个预处理指令来修改默认的对齐数 # pragma pack(x), 这个x就填入我们想修改的数值

在这里插入图片描述
如果我们想要只在一段使用这个对齐数,而在下一段恢复的话,可以这样使用

在这里插入图片描述


位段

讲完了内存对齐,下一个就来讲讲这个位段。

什么是位段

位段的声明和结构体是类似的,仅仅存在两个地方的不同:

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

例如:

	struct
	{
		int a : 1;
		int b : 3;
		int c : 5;
		int d : 31;
	}s3;

那么,它的大小是多少呢?在这里插入图片描述
为什么会是8呢?
因为一个整形是4个字节,而4个字节有32个比特位,前三个我们分别给的是1,3,5,加起来总共是9,没有达到32个比特位,而第四个占了31个,前三个已经无法在存放这个31,所以这个31单独存放在下一段空间中。总共占了两个整形的空间,所以是2* 4,八个字节。

位段的内存分配

  1. 位段的成员可以是int,unsigned int ,signed int, char类型
  2. 位段的空间上是按照需要以四个字节(int)或者一个字节(char)的方式来开辟的。
  3. 位段设计很多不确定因素,位段是不跨平台的,所以可移植的程序应该避免使用位段

在这里插入图片描述
对于这个位段,空间是如何开辟的呢?
因为一个字符型占据一个字节,而一个字节有八个比特位,所以我们需要先知道它们的二进制值
在这里插入图片描述
所有的二进制值我都写出来了,同时用框框框起来的是因为二进制数的位数大于我们的位段数,所以我们需要进行截断
在这里插入图片描述
这就是位段的存储方式。

位段的跨平台问题

上面的最后一点提到过对于跨平台的程序应该避免使用位段,这是为什么呢?

  1. in位段被当成有符号的还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义(这个就是我在上个月讲的那个大小端的问题)
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

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

发布了60 篇原创文章 · 获赞 78 · 访问量 6322

猜你喜欢

转载自blog.csdn.net/qq_35423154/article/details/103190437