【C语言】使用结构体实现位段

在这里插入图片描述

文章目录

  • 一、什么是位段
  • 二、位段的内存分配
    • 1.位段内存分配规则
    • 练习1
    • 练习2
  • 三、位段的跨平台问题
  • 四、位段的应用
  • 五、位段使用的注意事项

一、什么是位段

   在上一节中我们讲解了结构体,而位段的声明和结构是类似的,它们有两个不同之处,如下:

  1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他类型
  2. 位段的成员名后边有⼀个冒号和⼀个数字

比如:

struct A
{int a:2;int b:5;int c:10;int d:30;
};

   位段位段,那么变量后面的数字有没有可能就是二进制位,比如成员a是不是就是占据2个二进制位呢?
   我们可以通过计算这个位段的大小来试着查看,如图:
在这里插入图片描述
   如果它们都按整型算,那么应该是16个字节,现在却是8个字节,说明它还真的有可能是我们说的,每个成员后面是存储它们需要的二进制位
   但是出了一些意外,原本按二进制位算出来应该是47位,也就是6个字节,为什么这里是8个字节,我们慢慢往后学习就知道了

二、位段的内存分配

1.位段内存分配规则

位段的使用有以下三个需要注意的地方:

  1. 位段的成员可以是 int 、unsigned int 、signed int 或者是 char 等类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的,比如是整型int时就一次性开辟4个字节,是字符型char就一次型开辟1个字节
  3. 当开辟的空间不足时,才再次开辟对应的空间,不会一次性就开辟完所有空间,而是一个一个开辟

   接着我们通过举例说明它的特点,以及应用

练习1

#include <stdio.h>struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};int main()
{printf("%zd\n", sizeof(struct S));return 0;
}
//空间是如何开辟的?

   首先第一个成员是char类型,所以会开辟一个字节的空间,然后把a放进去,那么a这三个比特位到底怎么存放呢?从左边还是从右边开始使用?如图:
在这里插入图片描述
   位段从开辟空间的左边还是右边开始使用,这是C语言标准未规定的,也就是说可能有的编译器实现时,从左往右开始使用,而有的编译器在实现时,又是从右往左开始使用
   我们当前的编译器是VS2022,这个编译器在使用位段空间时,就是采用从右往左开始使用,那么按照这个逻辑我们看看能不能算出来位段S的最终大小
   所以当成员a放入位段时,应该是这个样子:
在这里插入图片描述

   接着我们继续看第二个成员b,它占据4个比特位,所以我们从右往左继续去存放b,如图:
在这里插入图片描述
   接着我们来看第三个成员c,它占用5个比特位,但是现在开辟的1个字节的空间不够用了,所以现在会像第2点说的,空间不够时再次开辟对应的空间,而这里的c是char类型,所以会再次创建一个字节,如图:
在这里插入图片描述
   那么此时问题又来了,我们的c是会继续使用第一个字节剩下的那个比特位,再加上第二个字节的4个比特位这种方式,还是会直接浪费掉第一个字节剩下的比特位,直接从右向左存放c
   这也是C语言未定义的,所以有的编译器可能会使用那剩余的比特位,而有的编译器不会使用,这完全取决于编译器
   所以现在我们可以按两种思路分别看看位段S算出来的大小,然后再运行代码,看当前VS采用的是哪种方法

  1. 使用剩余的那个比特位:此时我们的c应该占据如图空间:
    在这里插入图片描述
       然后这种情况下我们来看看最后一个成员d,它占据4个比特位,刚好可以放下,如图:
    在这里插入图片描述
       所以可以看到,在1这种情况下,位段S只需要2个字节来存储

  2. 不使用剩余的那个比特位:此时c的存放应该是这样的:
    在这里插入图片描述
    存放完c之后,我们来看看最后一个成员d,占4个比特位,很明显第二个字节已经不够用了,并且由于我们这种情况不会利用剩余的空间,所以还要继续开辟空间,这里d也是char,所以还是开辟一个字节的空间,然后把d放进去,如图:
    在这里插入图片描述
    所以可以看到,在2这种情况下,位段S需要3个字节来存储

   最后我们来看看这个代码在VS上运行的结果,看看最后VS中是使用1的方法,还是2的方法,如图:
在这里插入图片描述
   我们得到了方法2的结果,说明VS中很可能就不会使用剩余的比特位,将它浪费掉,为了进一步验证我们的想法,我们可以对这个位段赋值,如下:

#include <stdio.h>struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};int main()
{struct S s = {0};s.a = 10;s.b = 12;s.c = 3;s.d = 4;return 0;
}

   首先代码创建了一个位段s,将它初始化为了全0,如下:
在这里插入图片描述
   我们现在讲解时会把这些0暂时去掉方便观察,在最后如果有位置没有被修改,我们就重新把这些0画出来
   接下来就到给位段中的成员赋值了,我们将这些值按照刚刚方法2的思路放进去,然后通过调试查看内存中是否是这样存放
   现在我们开始存放这些数据,首先是存放a,10的二进制是1010,但是成员a只有3个比特位,所以会产生截断,最后变成010,将1截断了,我们来通过画图看看a现在是什么样子:
在这里插入图片描述
   接下来我们来看成员b,它存放的是12,对应的二进制是1100,因为b就是占用4个比特位,所以可以刚好存放完,如图:
在这里插入图片描述
   接下来我们来看c,它存放的是3,对应的二进制是11,由于c占用5个比特位,所以我们补0变成5位:00011,然后我们来看它的存储:
在这里插入图片描述
   最后来看d,它存放的是4,对应的二进制是100,由于d占用4位,所以补齐4位为:0100,然后我们来看看它的存储:
在这里插入图片描述
   由于我们最开始将位段s初始化为了0,也就是原本位段s中存储的是全0,而我们已经填写的位置就是在全0的基础上进行的修改,现在其它的位置没有受到影响,就还是初始化时的0,如下:
在这里插入图片描述
   由于调试中的内存窗口采用16进制显示,所以我们先把这三个字节的二进制转为十六进制,方法就是4位二进制转1位十六进制,如图:
在这里插入图片描述
   接下来我们在VS上调试这个代码,打开内存窗口,看看我们的想法和画的图是否正确,如下:
在这里插入图片描述
   可以看到最后内存中的结果和我们分析的一模一样,随后我们也可以得出最后的结论:VS中存放位段时,是从右往左存放,当空间不够时,会开辟新的空间,并且不会使用旧空间中的剩余比特位
   那么是不是所有编译器都是这样呢?这个就不一定了,因为这些都是C语言标准未规定的,我们在后面也会讲到

练习2

   这个练习就是我们开始讲位段的第一个例子,如下:

#include <stdio.h>struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};int main()
{printf("%zd\n", sizeof(struct A));return 0;
}

   我们当时运行出来是8个字节,我们来看看它是怎么来的
   首先第一个成员a是整型,所以会首先开辟4个字节,然后我们就按从右往左的顺序将a放进去,如图:
在这里插入图片描述
   现在开始存放b,b是5个比特位,剩余位数够用,可以直接放进去,如图:
在这里插入图片描述
   然后就是存放我们的c,c占据10个比特位,也是可以直接放进去的,如图:

在这里插入图片描述
   现在就差最后一个成员d,它占据30个比特位,而现在只剩下了32-2-5-10=15个比特位,很明显不够用了,所以我们还要继续开辟一个整型空间来存放d,如图:
在这里插入图片描述
   所以最后VS算出来应该就是2个整型的大小,也就是8个字节,最后我们再来看一次代码运行结果:
在这里插入图片描述

三、位段的跨平台问题

   位段虽然很节省空间,但是它却存在很大的跨平台问题,可移植性很低,因为在C语言标准中,位段的很多东西是没有规定的,导致各种编译器在实现它的时候,采用了不同的方法,我们现在来看看具体它的问题在哪里:

  1. int 位段被当成有符号数还是⽆符号数是不确定的,也就是说可能在有的编译器是有符号数,有的是无符号数,那么在把int位段当作有符号数的编译器上写的代码,可能就不能在把int位段当作无符号数的编译器上运行,或者运行后有bug存在,等等问题
  2. 位段中最⼤位的数⽬不能确定,因为32位机器的int是2个字节,也就是16个比特位,64位机器的int是4个字节,也就是32个比特位,如果把一个int位段成员写成27个比特位,那么它在32位上的机器就会出问题
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配,C语言标准没有定义,所以不同编译器就有不同的实现方法,而VS就是采用从右向左的方式,其它有的编译器可能就是从左向右分配空间,所以在不同编译器运行相同的位段结果也可能不同
  4. 当⼀个结构包含两个位段,第⼆个位段成员比较,无法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的,像VS就会舍弃那些剩余的位,直接使用新的空间

   所以综上4个理由,位段在使用的时候不一定能跨平台,也就是可移植性很低,但是相比于结构体,它能节省很多空间,我们继续学习就知道了

四、位段的应用

   位段的好处就是可以节省很多空间,它可以应用在对空间有严格要求的地方,我们现在举一个例子来进行说明:
   下图是⽹络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要⼏个bit位就能描述,如果使用整型就会浪费空间
   这⾥使⽤位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报大小也会较小⼀些,对网络的畅通是有帮助的
在这里插入图片描述
   可能有人会问,现在我们网络的速度非常快,并且电脑内存空间也非常大了,没有必要节省这么些空间吧?
   其实这是不太正确的,几个字节可能没有什么影响,但是由于网络上的数据量巨大,可能会有上亿个几个字节被浪费,并且这些空间会造成网络拥堵
   我们假设网络就是高速公路,而我们的ip数据包就是一辆辆的车,我们来比较一下它们不同大小的区别:
在这里插入图片描述
   很明显,当我们的ip数据包越大越容易造成网络拥堵,所以使用位段来节省这些空间是很有必要的,这就是位段的应用之一

五、位段使用的注意事项

   位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的,因为内存中的地址都是以字节为单位,⼀个字节内部的bit位是没有地址的
   所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员
   我们先来看错误示范:

#include <stdio.h>struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};int main()
{struct A sa = { 0 };scanf("%d", &sa.b);
}

   由于这里的b只占据了5个比特的空间,而地址都是以字节为单位的,所以这里的b并没有地址,使用&b就是错误的写法
   我们来看正确的写法:

#include <stdio.h>struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};int main()
{int b = 0;scanf("%d", &b);sa.b = b;return 0;
}

   这里我们重新创建了一个整型变量b,这个时候就可以对它取地址了,然后用户对b输入后,再录入我们位段的成员b,这样就可以使用位段存储用户输入的数据了

   结构体实现位段的全部内容我们都已经讲完了,可能有一点点难,只要多练习,自己去画画图,相信一定可以理解的,如果有什么问题欢迎提问
   那么今天的内容就到这里,bye~

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

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

相关文章

【重学 MySQL】四十四、相关子查询

【重学 MySQL】四十四、相关子查询 相关子查询执行流程示例使用相关子查询进行过滤使用相关子查询进行存在性检查使用相关子查询进行计算 在 select&#xff0c;from&#xff0c;where&#xff0c;having&#xff0c;order by 中使用相关子查询举例SELECT 子句中使用相关子查询…

【C++】认识匿名对象

文章目录 目录 文章目录前言一、对匿名对象的解读二、匿名对象的对象类型三、匿名对象的使用总结 前言 在C中&#xff0c;匿名对象是指在没有呗命名的情况下创建的临时对象。它们通常在单个语句中执行一系列操作或调用某个函数&#xff0c;并且不需要将结果存放进变量中。 匿名…

每日OJ题_牛客_AB13【模板】拓扑排序_C++_Java

目录 牛客_AB13【模板】拓扑排序 题目解析 C代码 Java代码 牛客_AB13【模板】拓扑排序 【模板】拓扑排序_牛客题霸_牛客网 (nowcoder.com) 描述&#xff1a; 给定一个包含nn个点mm条边的有向无环图&#xff0c;求出该图的拓扑序。若图的拓扑序不唯一&#xff0c;输出任意合法…

Matlab|基于遗传粒子群算法的无人机路径规划【遗传算法|基本粒子群|遗传粒子群三种方法对比】

目录 主要内容 模型研究 部分代码 结果一览 下载链接 主要内容 为了更高效地完成复杂未知环境下的无人机快速探索任务&#xff0c;很多智能算法被应用于无人机路径规划方面的研究&#xff0c;但是传统粒子群算法存在粒子更新思路单一、随机性受限、收敛速度慢…

【笔记】DDD领域驱动设计

同名读书笔记&#xff0c;对于一些自觉重要的点进行记录。 扩展资源&#xff1a;github.com/evancyz/ddd-learning UML中类图的一些基本知识 - jack_Meng - 博客园 最后的第四部分暂时没看 Part Two 模型驱动设计的构造块 Chapter 5 软件中所表示的模型 5.2 模式&#xff1a;…

学习日志35

拆卸线问题&#xff08;Disassembly Line Balancing Problem, DLBP&#xff09;是生产工程和运筹学中的一个特殊问题&#xff0c;它涉及到将废弃产品有效地拆解成可回收利用的部件和材料。随着环保意识的增强和资源回收技术的发展&#xff0c;DLBP逐渐成为研究的热点。这类问题…

MySQL中的嵌套查询

1. 嵌套查询的定义 嵌套查询指在一个查询语句的某个部分嵌入一个子查询。 嵌套查询的执行过程遵循“先子查询、后外层查询”的逻辑。首先&#xff0c;子查询执行并返回一个结果集&#xff0c;可能是一个值、一行或多行数据。接着&#xff0c;外层查询使用子查询的结果继续对数…

FireRedTTS - 小红书最新开源AI语音克隆合成系统 免训练一键音频克隆 本地一键整合包下载

小红书技术团队FireRed最近推出了一款名为FireRedTTS的先进语音合成系统&#xff0c;该系统能够基于少量参考音频快速模仿任意音色和说话风格&#xff0c;实现独特的音频内容创造。 FireRedTTS 只需要给定文本和几秒钟参考音频&#xff0c;无需训练&#xff0c;就可模仿任意音色…

【leetcode】 45.跳跃游戏 ||

如果我们「贪心」地进行正向查找&#xff0c;每次找到可到达的最远位置&#xff0c;就可以在线性时间内得到最少的跳跃次数。 例如&#xff0c;对于数组 [2,3,1,2,4,2,3]&#xff0c;初始位置是下标 0&#xff0c;从下标 0 出发&#xff0c;最远可到达下标 2。下标 0 可到达的…

如何在IDEA使用git上传代码的时候过滤掉非.java文件

1.情况分析 我们的java上传代码的时候&#xff0c;经常会出现这个xml,等等的无关文件&#xff0c;但是这个时候我们使用这个里面的git上传的时候无法过滤掉&#xff0c;我们在自己的这个代码仓库查看的时候经常显示无关文件&#xff0c;这个时候我们就可以通过相关配置进行文件…

MySQL连接查询:联合查询

先看我的表结构 emp表 联合查询的关键字&#xff08;union all, union&#xff09; 联合查询 基本语法 select 字段列表 表A union all select 字段列表 表B 例子&#xff1a;将薪资低于5000的员工&#xff0c; 和 年龄大于50 岁的员工全部查询出来 第一种 select * fr…

【PostgreSQL】运维篇—— 实战案例分析:从需求到实现

假设有一个电子商务平台&#xff0c;需要管理产品、订单和客户信息&#xff0c;如何设计数据库架构以满足业务需求&#xff0c;并实现相关的功能。 一、需求分析 1. 业务背景 某电子商务平台需要一个数据库来存储和管理以下信息&#xff1a; 产品信息&#xff1a;包括产品名…

Java版本的SSE服务端实现样例

简单记录一下使用netty方式实现SSE的服务端功能 目录 简要说明基于Netty功能需求后端代码1. 创建一个SpringBoot 应用2. 创建服务端功能3. 创建前端功能4. 测试SSE 封装为组件 简要说明 Server-Sent Events (SSE) 是一种用于在客户端和服务器之间建立单向通信的技术。 它允许服…

通信工程学习:什么是RFID射频识别

RFID&#xff1a;射频识别 RFID射频识别&#xff08;Radio Frequency Identification&#xff09;&#xff0c;又称为无线射频识别&#xff0c;是一种非接触式的自动识别技术。它通过无线电信号来识别特定目标并读写相关数据&#xff0c;而无需在识别系统与特定目标之间建立机械…

在安卓中使用 `mobile-ffmpeg` 压缩后的视频,浏览器在线播放提示“没有找到支持的视频格式和 MIME 类型”的解决方案

在安卓中使用 mobile-ffmpeg 压缩后的视频&#xff0c;浏览器在线播放提示“没有找到支持的视频格式和 MIME 类型”的解决方案 你可能在安卓开发中使用了 mobile-ffmpeg 进行视频压缩&#xff0c;而当你尝试在浏览器中在线播放压缩后的视频时&#xff0c;看到提示&#xff1a;…

任务【浦语提示词工程实践】

0.1 环境配置 首先点击左上角图标&#xff0c;打开Terminal&#xff0c;运行如下脚本创建虚拟环境&#xff1a; # 创建虚拟环境 conda create -n langgpt python3.10 -y 运行下面的命令&#xff0c;激活虚拟环境&#xff1a; conda activate langgpt 之后的操作都要在这个环境…

socket和http区别

socket和http区别&#xff1a;1、主体不同&#xff1b;2、所处层次不同&#xff1b;3、连接状态不同&#xff1b;4、传输数据量不同&#xff1b;5、数据安全性不同&#xff1b;6、连接方式不同。其中&#xff0c;主体不同指的是socke是一个调用接口&#xff08;API&#xff09;…

TypeScript 中函数的理解及其与 JavaScript 函数的差异

文章目录 一、函数概述二、TypeScript 函数的使用函数类型的声明可选参数剩余参数函数重载 三、TypeScript 与 JavaScript 函数的差异 一、函数概述 在 JavaScript 应用程序中&#xff0c;函数是核心组成部分&#xff0c;它们帮助我们实现代码的抽象、模拟类、隐藏信息和模块化…

【EXCEL数据处理】000013 案例 EXCEL筛选与高级筛选。

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】000013 案例 EXCEL筛选与高级筛选。使用的软件&#…

【华为OD机试真题】95、最少面试官数

package mainimport ("fmt""sort" )type s struct {start intend intworkCount int }type duration struct {start intend int }// 查询时间段内是否有可用的面试官 func getFreeS(sList []*s, d *duration, workCountLimit int) (sIndex int)…