C语言带你学会位段相关知识
作者:烽起黎明
一、什么是位段
位段的声明和结构是类似的,有两个不同:
- 位段的成员必须是
int
、unsigned int
或signed int
- 位段的成员名后边有一个冒号和一个数字
- 在下面,我分别写了一个结构体和一个位段,注意看位段的写法和结构体有什么不同
//结构体 struct A { int a; int b; int c; int d; }; //位段 struct B{ int _a : 2; int _b : 5; int _c : 10; int _d : 30; };
然后我们sizeof
去计算一下这个结构体的大小
printf("结构体大小:%d\n", sizeof(struct A)); printf("位段大小:%d\n", sizeof(struct B));
可以看到,结构体的大小是16,位段是8,二者为何会存在区别呢?原因在于这个: 2
吗?
- 那根据位段后面的这些数字,我们可以初步去断定可能大小是这些数组的总和,再转换为字节的。计算一下可以知道为
47b
,在内存中1B = 8b
,要存下这个47个比特位的话应该6个字节就够了,但是结果为什么是8呢?我们不得而知😐
学习了位段的相关知识后你就知道了
二、位段的内存分配
首先来科普一下位段的相关知识📖
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节
[int]
或者1个字节[char]
的方式来开辟的。 - 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
那从上面我们就可以提取出一些信息,知道了对于整型而言会开辟出4个字节的数据给到位段作为存放,那接下去呢我们就来分析一下这个位段
- 仔细观察可以得知每个成员都是整型,那首先开辟出32个比特位
_a
占了2个比特位,还剩下【30b】_b
占了5个比特位,还剩下【25b】_c
占了10个比特位,还剩下【15b】_d
占了30个比特位,但是剩下的【15b】不够用了,此时编译器会继续开辟出4B,也就是32b的空间来存放
所以最后的结果就是4 + 4 = 8B
struct B{ //4Byte - 32bit int _a : 2; //30 int _b : 5; //25 int _c : 10; //15 //4Byte - 32bit int _d : 30; //4 + 4 = 8 };
看了我上面的这样计算,你一定会有这些疑问
💬 第一次是32b用剩后的【15b】去哪儿了呢?
💬 _d
使用的是【15b】+ 后面开辟出来的32b,还是只用到后面的32b呢?
💬 难道所有平台都是这样吗?有没有不一样的计算方法?
上面是很多同学在课后提出来的疑问,关于这些,你在看完了我下面的分析后就会明白了👇 内存图分析位段分布
接下去我就通过对位段进行分析,然后观察内存分布来揭晓上面究竟是如何计算的。
为了方便期间,这里换一组位段,但是换汤不换药
struct S { char a : 3; char b : 4; char c : 5; char d : 4; }; int main(void) { struct S s = { 0 }; s.a = 10; s.b = 12; s.c = 3; s.d = 4; return 0; }
- 首先来看一下存放这个位段需要的字节数。可以看到这个位段中的每个成员都是
char
类型的,所以编译器会首先为其分配一个字节的空间,然后随着变量的存入,最终是需要三个字节
然后我们来逐一分析一下💻
- 刚才说了,这个位段在内存中需要开辟三个字节,这些变量要怎么存呢,首先看到变量a占了3个比特位,那是从左边的三位开始放还是右边的三位呢?总不可以从中间开始放吧!
那我们假设一下,从右边往左边放,那么a
放完后就是b
,占4个比特位,但是放c
的时候就放不下了,所以需要在开辟1个字节的空间,此时d
再来放的话也放不下了,所以也要再开辟1个字节 ,最后也就需要3个字节的空间
【详细分析如下】:
接下去我们就根据main函数中对位段各变量的初始化,来看看位段在内存中的分布情况:a
的初始值为10,不过这是十进制,转换为二进制形式的话就是[1010]
,转看位段这里a变量的是占了3位,所以会截断成010
,将它放到第一个字节处的右边3个比特位处
- 接下去是
b
,初始值为12
,转换为二进制形式的话就是[1100]
,而b在内存中也刚好是占4个比特位的大小,刚好第一个字节处还可以放得过,所以继续顺位放置 - 然后是
c
,初始值为3
,转换为二进制形式的话就是11
,但是c在内存中也占5个比特位的大小,所以要在前面做一个扩充便为[00011]
,但是第一个字节放不下了,上面放了【3】+【4】=【7】,只剩下1个比特位,那我们考虑再开一个字节的空间,为了保持连续性就直接把这个5个比特位的数据放到第二个字节的右边 - 最后的是
d
,初始值为4
,转换为二进制形式的话就是100
,不过d在内存中也占4个比特位的大小,所以要在前面补上一个0,即为[0100]
,但是第二个字节也放不过了,只剩三个比特位了,所以我们考虑再开一个字节的空间,然后放这个d
上面只是我假设的编译器执行思维,不过真正是怎样的,我们还是要求证一下
- 那要怎么求证呢?这个很简单,既然这些变量都是存放在位段中,那我们刚才都算出所存放的二进制形式了。对于内存中的地址一般我们看到都是十六进制,所以可以考虑把这些二进制4个为一组转换为十六进制看看
01100010
即为——>0x62
00000011
即为——>0x03
00000100
即为——>0x04
- 而在内存中左边是低地址,右边是高地址,所以我们看到的应该是
62 03 04 cc
。来通过【内存】观察一下吧
可以看到,确实和我们分析得是一模一样✌
看完了上面这个,相信你对一开始的那个位段如何去进行求解的整个流程应该是非常清楚了,留给读者自己的分析观察🔍
三、位段的跨平台问题
接下去我们再来讲讲有关位段的跨平台的问题
- int 位段被当成有符号数还是无符号数是不确定的
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32)
- 假设我们将位段中一个变量所占大小设置为30,即占30个比特位,那么它在32为机器上是没问题的,但是放到早期的16位机器上去的话,可能连编译都编不过,因为根本存放不下
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
- 刚才我分析的时候假设的是从右往左进行分配,但是呢这在其他平台上可能又是不一样的了
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
- 这也就是我们一开始纠结的【15】到底还用不用的问题,这里给出解答,还是不确定,取决于平台
总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在
- 这可能还有的老铁不太理解,举个例子:假设结构体A中的这个变量
a
只可能有【0】【1】【2】【3】这四种取值,那么只需要2个比特位就可以表达这四个数字了,即【00】【01】【10】【11】,那我们便可以使用位段来是实现:2
,但若是放在普通结构体中的话就只能是一个整型4个字节32个比特位的大小,这也就浪费了很多的空间 - 同理,若是变量
b
也只有5种表示形式的话,5个比特位就够了,c
、d
也是一样。那么这个时候位段就派上用场了,若是使用结构体的话就会浪费掉很多的空间。所以我们前面在看的时候,结构体所占的空间大小是16B,而位段只有8B
四、位段的应用
清楚了位段的相关知识和使用后,可能还是有同学比较迷惑这个位段到底是用来干嘛的,有什么实际应用场景吗?我们来看看
- 比方说这里有个IP数据包,有学习过《计算机网络》相关知识的读者应该都很清楚【不了解可以看看网络层知识点汇总】,我们平常在网络上和别人互相聊天的时候,所发送的消息并不是直接在网络链路上进行传送的,而是会将其封装到一个数据包中,它叫做IP数据包,例如我们所发送的
呵呵
只是里面的一个数据部分,还存在其他很多的字段,这些字段都占有各自的字节数 - 其实对于这些字节数来说,就是使用【位段】来实现,精准地控制好每个字段需要多少字节数,,就不会造成浪费的现象了
五、总结与提炼
最后来总结一下本文所学习的内容📖
- 在本文中,我们首先讲到了位段的相关概念,知道了原来使用结构体还可以实现位段,不过在看了二者的大小后,却产生了疑惑,为什么位段所占的大小是这些呢?
- 在清楚了位段在内容中的相关分布后,我带着读者一步步分析了位段中的成员数据到底是怎么一个个存放到内存中的,也通过VS中的【内存】验证观察了我们的分析结果,是正确的
- 然后便说道了位段这个东西其实具备很大的不确定性,因为它存在跨平台的问题,在不同平台下实现的机制可能不同,所以就会导致最后的位段大小会不一致
- 最后,也说道了位段的作用以及其实际的应用场景,让读者学以致用
到此这篇关于C语言带你学会位段相关知识的文章就介绍到这了,更多相关C语言位段知识内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!