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

news/2024/4/28 15:14:15/文章来源:https://blog.csdn.net/Fire_Cloud_1/article/details/130032355

在这里插入图片描述

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

一、什么是位段

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

  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 - 32bitint _a : 2;		//30int _b : 5;		//25int _c : 10;	//15//4Byte - 32bitint _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.luyixian.cn/news_show_283659.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手动构建自己的docker容器镜像实战

前言 之前的实战中,我们实战中,我们使用的镜像都是镜像仓库已有的镜像。 已有的镜像都是别人已经开发好上传的。今天我们一起来看看如何构建自己的镜像并上传到镜像仓库中。 🏠个人主页:我是沐风晓月 🧑个人简介&…

【Python】字符串 ⑤ ( Python 字符串快速格式化 | 不考虑变量类型 | 不考虑精度控制 )

文章目录一、Python 字符串快速格式化1、语法说明2、代码示例 - 不考虑变量类型3、代码示例 - 不考虑精度控制4、快速格式化的优点一、Python 字符串快速格式化 1、语法说明 Python 字符串快速格式化 : 通过如下格式的代码 , 可以进行字符串的快速格式化 ; f"字符串内容{…

vscode代码片段生成

在刚学习vue的时候,有些代码片段是经常写的,在vscode中写一个代码片段可以帮助快速生成。 生成步骤: VSCode中的代码片段有固定的格式,所以我们一般会借助于一个在线工具来完成。 具体的步骤如下: 第一步,复制自己需…

〖Python网络爬虫实战⑨〗- 正则表达式基本原理

订阅:新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列(零基础小白搬砖逆袭) 说明:本专栏持续更新中,目前专栏免费订阅,在转为付费专栏前订阅本专栏的,可以免费订阅付…

Mac PicGo可以上传GitHub但是不能显示

Mac PicGo可以上传到GitHub但是本地不能显示(已经加载的)图片 背景:使用Typora PicGo GitHub 图床。 文章目录Mac PicGo可以上传到GitHub但是本地不能显示(已经加载的)图片1. Bug表现2. 解决方法(1&…

【好书推荐】认知觉醒:开启自我改变的原动力

书籍信息 书名:认知觉醒:开启自我改变的原动力 作者: 周岭 出版社: 人民邮电出版社 认知觉醒的基础 重新认识大脑 在我们的大脑里,由内到外至少有三重大脑:年代久远的本能脑、相对古老的情绪脑和非常年…

【C语言深入】逐汇编详解函数栈帧的创建和销毁过程

【C语言深入】逐汇编详解函数栈帧的创建和销毁过程一、图解大概过程二、函数栈帧的创建过程1、简介一些需要用到的汇编指令和寄存器2、调用main函数的函数3、局部变量的初始化4、形成临时拷贝5、函数调用6、形成栈帧7、提取临时拷贝8、return返回三、函数栈帧的销毁过程1、释放…

python:异常处理与文件操作(知识点详解+代码展示)

文章目录一、异常处理1、try...except语句2、finally语句二、断言1、定义2、举例例一:例二:三、文件操作1、写文件操作2、读文件操作(当你心情低落时候,记得外面还有美好的风景!) 学习目标: 1、…

堆相关的面试题

文章目录1. 距离不超过k的推排序2. 最大线段重合问题1. 距离不超过k的推排序 题目:已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k,并且k相对于数组长度来说是比较小的。 请选择一…

WinRAR压缩解压文件

使用WinRAR压缩管理器压缩解压文件详细步骤如下: ■ 压缩文件 ① 鼠标右键需要压缩的文件,点击“添加到压缩文件”,具体操作步骤如图所示: ② 压缩后的对应文件压缩包会显示在桌面,如图所示: ■ 解压文件 …

如何设计一个高并发系统

目录 如何理解高并发系统 1. 分而治之,横向扩展 2. 微服务拆分(系统拆分) 3. 分库分表 4. 池化技术 5. 主从分离 6. 使用缓存 7. CDN——加速静态资源访问 8. 消息队列——削锋 9. ElasticSearch 10. 降级熔断 11. 限流 12. 异步…

【OpenLayers】VUE+OpenLayers+ElementUI加载WMS地图服务

【OpenLayers】VUEOpenLayersElementUI加载WMS地图服务准备工作安装vue创建vue项目安装OpenLayers安装ElementUI加载wms地图服务准备工作 需要安装好nodejs,nodejs下载地址,下载对应的版本向导式安装即可。 安装完成后,控制台输入node -v&a…

【CentOS 7安装MySQL 8的教程指南】

CentOS 7安装MySQL 8 添加MySQL官方源 wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm sudo rpm -ivh mysql80-community-release-el7-3.noarch.rpm安装MySQL 8 sudo yum install mysql-community-server安装失败执行下面的命令并再次执行安装…

进程与线程的区别和联系

进程与线程的区别和联系🔎进程🔎线程🔎进程与线程的联系🔎进程与线程的区别🔎总结🔎 结尾🔎进程 进程简单来说就是运行着的程序 如果不太理解可以参考一下这篇文章 进程 🔎线程 …

【MySQL--01】数据库基础

文章目录1.什么是数据库2.主流数据库2.1 MySQLMySQL架构实例3.基本使用3.1 MySQL的安装3.2 连接服务器3.3服务器管理4.服务器,数据库,表之间的关系5.使用数据库6.SQL分类7.存储引擎查看存储引擎存储引擎对比1.什么是数据库 数据库是用来存储数据的。那么…

Java BigDecimal学习

文章目录Java BigDecimal不损失精度的方法Java BigDecimal的几种舍入模式1、UP(BigDecimal.ROUND_UP)2、DOWN(BigDecimal.ROUND_DOWN)3、CEILING(BigDecimal.ROUND_CEILING)4、FLOOR(BigDecimal.ROUND_FLOOR)5、HALF_UP(BigDecimal.ROUND_HALF_UP)6、HALF_DOWN(BigDecimal.ROUN…

QMake宏定义常量和字符串或带空格的字符串(在代码中使用)

答案 宏定义常量 DEFINES EXPIR_TIME123宏定义字符串(不带空格) DEFINES NIHAO\\\"nihao\\\"宏定义字符串(带空格也适用于不带空格的情况) 推荐 DEFINES NIHAO\"\\\"ni" "hao\\\"\"QMAKE宏定义常量 环境: visual studio 2018 …

Java基础之List

文章目录一、List介绍二、List常用方法 List应知应会2.1 调用add()方法增添数据(可指定位置添加)2.2 调用remove()方法删除指定位置元素并返回被删除元素2.3 调用set()方法修改指定位置元素并返回初始数据2.4 调用get()方法返回指定位置元素三、List可重…

SQL注入写入文件方法(获取webshell)

数据库写入文件条件 1、当前数据库用户为 root 权限2、知道当前网站的绝对路径3、secure_file_priv 的参数必须为空或目录地址4、PHP的 GPC 为 off状态;(魔术引号,GET,POST,Cookie)用 sqli-labs 测试查看当前用户权限Python sqlma…

本机连接Vmware虚拟机中win7的SQLServer数据库

在开发中,可能遇到不同数据库或不同版本的问题,为了避免在本机安装卸载造成后续无法再次安装的情况,我们在虚拟机中安装需要的版本进行测试。 本篇介绍如何在本机连接到虚拟机中的数据库。 解决流程如下: 一:进入虚…