C语言程序设计 | 结构体内存对齐,位段

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

内存对齐

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

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

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

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

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

. vs的默认值为8,linux默认值为4(32位系统下由于数据总线只有32位,每次只能读取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. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

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

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌桓丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值