C生万物 | 十分钟带你学会位段相关知识

news2024/11/6 9:56:48

在这里插入图片描述

在这里插入图片描述
结构体相关知识可以先看看这篇文章 —— 链接

一、什么是位段

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

  1. 位段的成员必须是 intunsigned intsigned int
  2. 位段的成员名后边有一个冒号和一个数字
  • 在下面,我分别写了一个结构体和一个位段,注意看位段的写法和结构体有什么不同
//结构体
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呢?我们不得而知😐

在这里插入图片描述

学习了位段的相关知识后你就知道了

二、位段的内存分配

首先来科普一下位段的相关知识📖

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节[int]或者1个字节 [char] 的方式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
  • 那从上面我们就可以提取出一些信息,知道了对于整型而言会开辟出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。来通过【内存】观察一下吧

可以看到,确实和我们分析得是一模一样✌
在这里插入图片描述


看完了上面这个,相信你对一开始的那个位段如何去进行求解的整个流程应该是非常清楚了,留给读者自己的分析观察🔍

三、位段的跨平台问题

接下去我们再来讲讲有关位段的跨平台的问题

  1. int 位段被当成有符号数还是无符号数是不确定的
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32)
    • 假设我们将位段中一个变量所占大小设置为30,即占30个比特位,那么它在32为机器上是没问题的,但是放到早期的16位机器上去的话,可能连编译都编不过,因为根本存放不下
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
    • 刚才我分析的时候假设的是从右往左进行分配,但是呢这在其他平台上可能又是不一样的了
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
    • 这也就是我们一开始纠结的【15】到底还用不用的问题,这里给出解答,还是不确定,取决于平台

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

  • 这可能还有的老铁不太理解,举个例子:假设结构体A中的这个变量a只可能有【0】【1】【2】【3】这四种取值,那么只需要2个比特位就可以表达这四个数字了,即【00】【01】【10】【11】,那我们便可以使用位段来是实现:2,但若是放在普通结构体中的话就只能是一个整型4个字节32个比特位的大小,这也就浪费了很多的空间
  • 同理,若是变量b也只有5种表示形式的话,5个比特位就够了,cd也是一样。那么这个时候位段就派上用场了,若是使用结构体的话就会浪费掉很多的空间。所以我们前面在看的时候,结构体所占的空间大小是16B,而位段只有8B

在这里插入图片描述

四、位段的应用

清楚了位段的相关知识和使用后,可能还是有同学比较迷惑这个位段到底是用来干嘛的,有什么实际应用场景吗?我们来看看

  • 比方说这里有个IP数据包,有学习过《计算机网络》相关知识的读者应该都很清楚【不了解可以看看网络层知识点汇总】,我们平常在网络上和别人互相聊天的时候,所发送的消息并不是直接在网络链路上进行传送的,而是会将其封装到一个数据包中,它叫做IP数据包,例如我们所发送的呵呵只是里面的一个数据部分,还存在其他很多的字段,这些字段都占有各自的字节数
  • 其实对于这些字节数来说,就是使用【位段】来实现,精准地控制好每个字段需要多少字节数,,就不会造成浪费的现象了

在这里插入图片描述

五、总结与提炼

最后来总结一下本文所学习的内容📖

  • 在本文中,我们首先讲到了位段的相关概念,知道了原来使用结构体还可以实现位段,不过在看了二者的大小后,却产生了疑惑,为什么位段所占的大小是这些呢?
  • 在清楚了位段在内容中的相关分布后,我带着读者一步步分析了位段中的成员数据到底是怎么一个个存放到内存中的,也通过VS中的【内存】验证观察了我们的分析结果,是正确的
  • 然后便说道了位段这个东西其实具备很大的不确定性,因为它存在跨平台的问题,在不同平台下实现的机制可能不同,所以就会导致最后的位段大小会不一致
  • 最后,也说道了位段的作用以及其实际的应用场景,让读者学以致用

以上就是本文要介绍的所有内容,感谢您的阅读,如果觉得有帮助的话,可以给个三连哦❤️❤️❤️

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/428820.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式51单片机01-开发版介绍与LED流水灯系列

文章目录1. STC89C52单片机介绍2. 单片机预备知识3. LED灯操作系列1. 点亮LED(1)LED原理图连接方式(2) proteus接线图(3)keil代码2. LED灯闪烁(1)实验电路图(2&#xff0…

每日一题104——转置矩阵

给你一个二维整数数组 matrix, 返回 matrix 的 转置矩阵 。 矩阵的 转置 是指将矩阵的主对角线翻转,交换矩阵的行索引与列索引。 示例 1: 输入:matrix [[1,2,3],[4,5,6],[7,8,9]] 输出:[[1,4,7],[2,5,8],[3,6,9]] 示…

大佬们隔空互怼

阅读本文大概需要 1.14 分钟。1、最近国内各大互联网公司都在掀起一股大模型热潮,要想让这股热潮变得更持久更精彩,离不开互联网大佬们的添砖加瓦。比如上周搜狗的王小川跟百度的肖阳,两位互联网大佬隔空互怼的场景,一度让大家觉得…

时序分析 49 -- 贝叶斯时序预测(一)

贝叶斯时序预测(一) 时序预测在统计分析和机器学习领域一直都是一个比较重要的话题。在本系列前面的文章中我们介绍了诸如ARIMA系列方法,Holt-Winter指数平滑模型等多种常用方法,实际上这些看似不同的模型和方法之间都具有千丝万缕…

SpringBoot中处理日期的两种方式(消息转换器)

在Spring Boot中,我们通常会使用Jackson来序列化和反序列化Java对象到JSON。在进行日期序列化时,我们需要指定日期格式,否则Jackson会使用默认格式,这可能不是我们想要的。要指定日期格式,有两种方式: 如果没有处理按照默认的结果如下图: 所以我们需要通过一些手段来对日期进行…

总结一下Redis的缓存雪崩、缓存击穿、缓存穿透

缓存是提高系统性能的一种常见手段,其中Redis是一种常用的高性能缓存数据库。但是在使用缓存时,可能会遇到一些问题,比如缓存击穿、缓存穿透、缓存雪崩等问题,本文将介绍这些问题的概念、原因以及解决方案。 缓存击穿 缓存击穿指…

Jenkins——用户管理、授权策略配置以及Jenkins安全配置管理

这里写目录标题一、Jenkins用户管理1、进入系统管理界面2、创建用户3、编辑用户信息4、编辑用户信息5、删除用户信息二、Jenkins授权策略配置1、授权策略插件2、安装插件:Role-based Authorization Strategy3、管理角色a、全局角色 Global rolesb、项目角色 Item rolesc、节点角…

CVPR | 达摩院开源自监督学习框架CoKe, 单机8卡可训练

团队模型、论文、博文、直播合集,点击此处浏览 一、论文 论文链接: Unsupervised Visual Representation Learning by Online Constrained K-Means 代码链接:https://github.com/idstcv/CoKe 二、背景 虽然基于instance的自监督学习方法在ImageNet上…

注册claude AI账号 slack工作区账号

Claude 是建立在 slack工作区的一个AI人工助手,更像是将chatgpt集成到了会议模式,一个账号实际上拥有了你的会议室和你的AI助手,你可以让你的朋友和同事进入你的房间体验。 Claude是不是openai的产物?目前还不知道,不…

RS232/RS485/RS422 接线说明

RS232/RS485/RS422 接线 文章目录RS232/RS485/RS422 接线RS232RS485422RS232 设备A 和 设备B 没有硬件流控 设备A设备BTXDRXDRXDTXDGNDGND 设备A 和 设备B 使用RTS/CTS 做硬件流控 一般使用RTS/CTS都会由232芯片自动控制, 比如XR21V1414, 需要设备gpio mode 为00…

Docker安装+利用docker安装MySQL(保姆级教程)

前言:本文基于docker安装MySQL,如果还没安装docker的童鞋,点击这里(超大声)安装卸载老版本docker(超详细) 一、在docker仓库搜你想要的镜像版本 docker镜像仓库 打开官网,在搜索框…

Docker 镜像制作 服务编排 私有仓库

DockerNginx部署Redis部署Dockerfile镜像制作容器转为镜像dockerfileDocker服务编排Docker ComposeDocker Compose安装使用docker compose编排nginxspringboot项目Docker私有仓库私有仓库搭建将镜像上传至私有仓库Nginx部署 案例:需求 在Docker容器中部署Nginx&…

【ARMv8 异常模型入门及渐进2 - 系统寄存器访问方法:op1,CRn,CRm,op2】

文章目录1.1 ARMv8 系统寄存器访问概要1.1.1 系统寄存器访问级别1.1.2 ARMv8 系统寄存器编码1.1 ARMv8 系统寄存器访问概要 ARMv8中,取消了协处理器,之前协处理器实现的功能,全部由系统寄存器来是实现。对于系统寄存器的访问,使用…

javaweb在校大学生贷款管理系统ns08a9

1系统主要实现:学生注册、填写详细资料、申请贷款、学校审核、银行审核、贷后管理等功能, (1) 学生注册:学生通过注册用户,提交自己的详细个人资料,考虑现实应用中的安全性,资料提交后不可修改;…

Springboot + MySQL+ JPA Ⅲ delete方法详解

一、deleteById(Id id) 和 delete(T entity) 为什么要把这两个方法放在一起呢?我们先看源码再说 deleteById源码(通过id进行删除) Transactional Override public void deleteById(ID id) {Assert.notNull(id, ID_MUST_NOT_BE_NULL);delet…

@爱打游戏的你,当游戏测试是什么感觉?

爱打游戏的你,当游戏测试是一种什么感觉? 去年《宝可梦朱紫》大火的那段时间,想必各位爱好游戏的友友们都刷到过这样的图吧:(量子纠缠)(天怎么黑了)(弹簧巨怪&#xff09…

手敲Mybatis(九)-结果集处理器

1.前言-背景介绍 上节我们处理了参数处理器,本节我们处理结果集处理器,之前我们写了一个DefaultResultSetHandler,我们把返回结果获取对象,填充值什么的写到了一起,流程没有进行解耦,并且只接收了Object的…

不通过鲁大师进行硬件检测

文章目录简介dxdiag系统信息设备管理器任务管理器PowerShelldxdiag系统信息设备管理器任务管理器PowerShellTODO:CPU-ZTODO:Everest参考文献简介 不少二手电脑重装系统后发现是假配置(特别是XP系统),可以使用系统自带…

基于Vue2实现滚动过程中数据懒加载

以下为实现滚动懒加载的过程: 1、在data对象中定义了items数组,用于存放已加载的item,loading状态,当前页数page,每页数量pageSize,以及距离底部的阈值threshold。 2、在mounted钩子函数中,首次…

Adaptive AUTOSAR——State Management(VRTE 3.0 R21-11)

状态管理是自适应平台服务中的一个功能集群。 在自适应平台中,状态决定了一组活动的自适应应用程序。 特定于项目的应用程序,即状态管理器,决定何时请求状态更改,从而更改当前活动的应用程序集。状态管理器是特定于项目的&#…