【自定义类型详解】第二篇——结构体实现位段

前言

上一篇文章,我们一起学习了结构体,那学完了结构体,就一定得讲讲结构体实现位段的能力。
这篇文章,我们再来一起学习一个新知识——位段。
一起来学习吧!!!

1.什么是位段

首先,我们一起来了解一下什么是位段。

位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。

位段和结构体其实是非常相似的,但是有两个不同点:

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

举个例子:

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

A就是一个位段类型。

那里面的冒号和数字又表示什么呢?

首先我们要明白位段中的这个“位”字其实指的是二进制位。
我们知道一个二进制位就是1个比特位。
所以,A中int _a : 2;其实表示的就是
_a的大小是2bit;
同理:
_b的大小是5bit
_c的大小是10bit
_d的大小是30bit

那我们再来思考一个问题:

位段A的大小应该是多少呢?
是30+10+5+2=47个bit吗,但我们知道sizeof计算出来的是字节数啊,所以会给它分配6个字节=48个比特位吗。

我们验证一下:
在这里插入图片描述
是8个字节哎,为什么呢?
别着急,我们接着往下看。

2.位段的内存分配

我们已经知道:

位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型。

那位段所需要的空间是怎么来开辟的呢?

位段的空间上是按照需要以每次4个字节( int )或者1个字节( char )的方式来开辟的。

什么意思呢,解释一下:

就是说,如果位段的成员全部是整型的(位段成员一般都是同类型的),那上去就先给这个位段开辟4个字节的空间,如果不够用,放不下所有的成员,那就再开辟4个字节的空间,还不够用,继续开辟,以此类推。
如果成员全部是char类型的,那就一次开辟1个字节的空间,直至放得下所有成员。

好,那我想现在大家就明白为什么上面位段struct A的大小是8个字节了。

由于A的成员都是整型(int ),所以一次给A分配4个字节。
4个字节是32给比特位,A的前3个成员_a、_b、_c占了17个bit,32-17还剩15bit,但是A的第四个成员_d大小是30bit,而15<30不够。
怎么办?
再分配4个字节,这下就能放下_d,因此,struct A的大小是4+4=8个字节。

那现在又有一个问题:
对于刚才讨论过的struct A来说,上来先给它分配了4个字节及32比特位,前3个成员占了17个bit,32-17还剩15bit,然后这15bit留给第四个成员_d不够用,所以又开辟4个字节。

那这15bit以及新开辟的4个字节的空间是怎样给第四个成员_d分配的,有两种情况:

  1. _d的大小是30bit,它先使用了前面剩下的15bit,然后又使用了新开辟的4个字节中的15bit。
  2. 前面剩下的15bit,就直接浪费掉不用了,_d直接使用新开辟的4个字节中的30个bit。

那到底采用的是哪种呢?其实C语言的语法也并没有做出明确的规定:

位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

3.验证vs环境下位段成员如何分配内存空间

虽然是不确定的,但是我们可以来验证一下,在我们当前使用的环境(vs2022)下,位段开辟的空间是怎么分配给每个成员的。
接下来,我们就通过一个实例来探究一下:

struct S 
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct S s1 = { 0 };
	return 0;
}

我们先来思考一下,s1 的大小是多少,并猜测一下这些空间是如何分配给每个成员的。

s1 的每个成员都是char类型的,所以应该每次开辟1个字节。

  1. 首先分配的一个字节8个比特位,是可以放得下a,b的,我们假设是这样放的:
    在这里插入图片描述
  1. 再放c的话,只剩1个比特位,就不够了,所以需要再开辟1个字节的空间,那剩下的这1个比特位用不用呢,反正是不确定的,这里我们就假设再vs上不再使用剩下的这1个比特位了。
    在这里插入图片描述

3.那接着就要再开辟一个字节,因为还有一个成员
在这里插入图片描述

那vs环境下的位段变量s1的大小会是3个字节吗?
我们验证一下:
在这里插入图片描述
确实是3,证明我们的猜测大致是正确的。
接下来,我们再来进一步的验证一下:

int main()
{
	struct S s1 = { 0 };
	s1.a = 10; 
	s1.b = 12; 
	s1.c = 3; 
	s1.d = 4;
	printf("%d", sizeof(s1));
	return 0;
}

我们先把位段变量s1 的成员都赋值为0,然后给他们重新赋一个非0值,最后,我们借助编译器观察一下,各个成员再内存中的存放是不是跟我们上面分析的一样。

在这里插入图片描述

在这里插入图片描述

现在我们通过编译器观察一下:
在这里插入图片描述
结果验证了在vs上位段的内存分配就是这样搞得,那在其它的平台上是不是也是这样呢?

不是的,前面已经提到了,这是不确定的,是标准未定义,在其他编译器上,可能结果就不一定是这样了。
位段是不跨平台的。

4.位段的跨平台问题

上面我们提到位段是不跨平台的,那接下来我们就来讨论讨论位段的跨平台问题。

1. int 位段被当成有符号数还是无符号数是不确定的。
什么意思呢,解释一下:

我们还拿上面的代码来说

struct A 
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};

位段A的成员都是int 类型的,但是我们这样直接给一个int ,它到底会被当成有符号int 还是无符号int 是不确定的

2. 位段中最大位的数目不能确定。
什么意思呢,再把上面的代码拿过来:

struct A 
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};

这里面成员_d给的大小是30比特位。
但是在16位的机器上,int 类型的大小是2个字节=16个比特位,即在16位的机器上最大的位数才16位
那如果我们把int _d:30;的代码放在16位机器上,是不是就出错了啊。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
再来看一张上面用过的图:
在这里插入图片描述

我们当时为什么这样放,是不是我们假设的啊,我们假设位段的成员再内存中是从右向左分配的。
为什么假设,因为这时标准未定义的,在不同的平台上可能就是不一样的。

4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
这个问题我们其实在上面也已经提到过了,再来看一张上面用过的图:
在这里插入图片描述

这张图上,当剩余的空间无法容纳后面的位段成员时,我们就是把剩余的空间舍弃了,去使用新开辟的空间来放后面的成员,而在vs上也就是这样做的。
当然它也是不确定的,在不同平台可能不同

总结

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

struct A //位段
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
struct B //结构体
{
	int a;
	int b;
	int c;
	int d;
};
int main()
{
	printf("%d\n", sizeof(struct B));
	printf("%d\n", sizeof(struct A));
	return 0;
}

比较一下它们的大小:
在这里插入图片描述

以上内容就是对结构体实现位段的一个讲解,欢迎大家指正!!!
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

YIN_尹

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

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

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

打赏作者

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

抵扣说明:

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

余额充值