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

文章目录
- 一、什么是位段
- 二、位段的内存分配
- 1.位段内存分配规则
- 练习1
- 练习2
- 三、位段的跨平台问题
- 四、位段的应用
- 五、位段使用的注意事项
一、什么是位段
在上一节中我们讲解了结构体,而位段的声明和结构是类似的,它们有两个不同之处,如下:
- 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他类型
- 位段的成员名后边有⼀个冒号和⼀个数字
比如:
struct A
{int a:2;int b:5;int c:10;int d:30;
};
位段位段,那么变量后面的数字有没有可能就是二进制位,比如成员a是不是就是占据2个二进制位呢?
我们可以通过计算这个位段的大小来试着查看,如图:

如果它们都按整型算,那么应该是16个字节,现在却是8个字节,说明它还真的有可能是我们说的,每个成员后面是存储它们需要的二进制位
但是出了一些意外,原本按二进制位算出来应该是47位,也就是6个字节,为什么这里是8个字节,我们慢慢往后学习就知道了
二、位段的内存分配
1.位段内存分配规则
位段的使用有以下三个需要注意的地方:
- 位段的成员可以是 int 、unsigned int 、signed int 或者是 char 等类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的,比如是整型int时就一次性开辟4个字节,是字符型char就一次型开辟1个字节
- 当开辟的空间不足时,才再次开辟对应的空间,不会一次性就开辟完所有空间,而是一个一个开辟
接着我们通过举例说明它的特点,以及应用
练习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采用的是哪种方法
-
使用剩余的那个比特位:此时我们的c应该占据如图空间:

然后这种情况下我们来看看最后一个成员d,它占据4个比特位,刚好可以放下,如图:

所以可以看到,在1这种情况下,位段S只需要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语言标准中,位段的很多东西是没有规定的,导致各种编译器在实现它的时候,采用了不同的方法,我们现在来看看具体它的问题在哪里:
- int 位段被当成有符号数还是⽆符号数是不确定的,也就是说可能在有的编译器是有符号数,有的是无符号数,那么在把int位段当作有符号数的编译器上写的代码,可能就不能在把int位段当作无符号数的编译器上运行,或者运行后有bug存在,等等问题
- 位段中最⼤位的数⽬不能确定,因为32位机器的int是2个字节,也就是16个比特位,64位机器的int是4个字节,也就是32个比特位,如果把一个int位段成员写成27个比特位,那么它在32位上的机器就会出问题
- 位段中的成员在内存中从左向右分配,还是从右向左分配,C语言标准没有定义,所以不同编译器就有不同的实现方法,而VS就是采用从右向左的方式,其它有的编译器可能就是从左向右分配空间,所以在不同编译器运行相同的位段结果也可能不同
- 当⼀个结构包含两个位段,第⼆个位段成员比较,无法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的,像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~
相关文章:
【C语言】使用结构体实现位段
文章目录 一、什么是位段二、位段的内存分配1.位段内存分配规则练习1练习2 三、位段的跨平台问题四、位段的应用五、位段使用的注意事项 一、什么是位段 在上一节中我们讲解了结构体,而位段的声明和结构是类似的,它们有两个不同之处,如下&…...
univer实现excel协同
快速入门 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><script src&q…...
JavaScript进阶笔记--深入对象-内置构造函数及案例
深入对象 创建对象三种方式 利用对象字面量new Object({…})利用构造函数 // 1. 字面量创建对象const obj1 {name: pig,age: 18};console.log(obj1); // {name: "pig", age: 18}// 2. 构造函数创建对象function Pig(name, age) {this.name…...
网络爬虫自动化Selenium模拟用户操作
自动化测试和网络爬虫在现代软件开发中占据着重要的位置。它们通过自动化用户操作,减少了人工重复操作的时间成本。Selenium作为一个功能强大且应用广泛的自动化工具,不仅能在不同的浏览器中运行自动化测试,还能进行跨平台测试,并允许与多种编程语言集成。本教程将介绍如何…...
尚硅谷rabbitmq 2024 流式队列2024指定偏移量 第55节答疑
rabbitmq的stream: 4、对比 autoTrackingstrategy方式:始终监听Stream中的新消息(狗狗看家,忠于职守)指定偏移量方式:针对指定偏移量的消息消费之后就停止(狗狗叼飞盘,回来就完) 这两种分别怎么写?java 在 RabbitMQ 中,…...
NSSCTF-WEB-pklovecloud
目录 前言 正文 思路 尝试 结尾 前言 许久未见,甚是想念. 今天来解一道有意思的序列化题 正文 思路 <?php include flag.php; class pkshow {function echo_name(){return "Pk very safe^.^";} }class acp {protected $cinder;public $neutron;public $…...
深入Postman- 自动化篇
前言 在前两篇博文《Postman使用 - 基础篇》《玩转Postman:进阶篇》中,我们介绍了 Postman 作为一款专业接口测试工具在接口测试中的主要用法以及它强大的变量、脚本功能,给测试工作人员完成接口的手工测试带来了极大的便利。其实在自动化测试上,Postman 也能进行良好的支…...
react-JSX
JSX理念 jsx在编译的时候会被babel编译为react.createELement方法 在使用jsx的文件中,需要引入react。import React from "react" jsx会被编译为React.createElement,所有jsx的运行结果都是react element React Component 在react中,常使用…...
深度对比:IPguard与Ping32在企业网络管理中的应用
随着网络安全形势日益严峻,企业在选择网络管理工具时需慎之又慎。IPguard与Ping32是目前市场上两款颇具代表性的产品,它们在功能、性能以及应用场景上各有优势。本文将对这两款产品进行深度对比,以帮助企业找到最合适的解决方案。 IPguard以其…...
AI测试之 TestGPT
如今最火热的技术莫非OpenAI的ChatGPT莫属,AI技术也在很多方面得到广泛应用。今天我们要介绍的TestGPT就是一个软件测试领域中当红的应用。 TestGPT是什么? TestGPT是一家总部位于以色列特拉维夫的初创公司 CodiumAI Ltd.,发布的一款用于测…...
JavaEE-进程与线程
1.进程 1.1什么是进程 每个应⽤程序运⾏于现代操作系统之上时,操作系统会提供⼀种抽象,好像系统上只有这个程序在运 ⾏,所有的硬件资源都被这个程序在使⽤。这种假象是通过抽象了⼀个进程的概念来完成的,进程可 以说是计算机科学…...
JAVA软开-面试经典问题(6)-equals与hashcode方法
1.equals方法 1.Object类中的equals方法比较的是两个对象的地址(底层原理是 比较的,即比较的是对象的地址) return (this obj);2.基本数据类型的包装类和String类都重写了equals方法。 基本数据类型:比较的是数值的是否相等 …...
计算机网络(以Linux讲解)
计算机网络 网络协议初识协议分层OSI七层模型TCP/IP五层模型--初识 网络中的地址管理IP地址MAC地址 网络传输基本流程网络编程套接字预备知识网络字节序socket编程UDP socketTCP socket地址转换函数Jsoncpp 进程间关系与守护进程进程组会话控制终端作业控制守护进程 网络命令TC…...
计算机网络基本架构知识点
1. 网络体系结构模型: - OSI 七层模型: - 物理层:是网络通信的基础层,负责在物理介质上传输比特流。该层定义了物理连接的标准,如电缆的类型、接口的形状、插头的规格等,以及信号的传输方式,包括…...
GES DISC 的 ATMOS L2 潜在温度网格上的痕量气体,固定场格式 V3 (ATMOSL2TF)
ATMOS L2 Trace Gases on Potential Temperature Grid, Fixed Field Format V3 (ATMOSL2TF) at GES DISC 简介 GES DISC 的 ATMOS L2 潜在温度网格上的痕量气体,固定场格式 V3 (ATMOSL2TF) 这是版本3的气溶胶痕量分子光谱(ATMOS)第二级产品…...
MLCC贴片电容不同材质区别:【及电容工作原理】
贴片电容的材质常规有:NPO(COG),X7R,X5R,Y5V 等,主要区别是它们的填充介质不同。在相同的体积下由于填充介质不同所组成的电容器的容量就不同,随之带来的电容器的介质损耗、容量稳定…...
Word粘贴时出现“文件未找到:MathPage.WLL”的解决方案
解决方案 一、首先确定自己电脑的位数(这里默认大家的电脑都是64位)二、右击MathType桌面图标,点击“打开文件所在位置”,然后分别找到MathPage.WLL三、把这个文件复制到该目录下:C:\Program Files\Microsoft Office\r…...
前端开发笔记--html 黑马程序员1
文章目录 前端开发工具--VsCode前端开发基础语法VsCode优秀插件Chinese --中文插件Auto Rename Tag --自动重命名插件open in browserOpen in Default BrowserOpen in Other Browser Live Server -- 实时预览 前端开发工具–VsCode 轻量级与快速启动 快速加载:VSCo…...
ARM/Linux嵌入式面经(四四):华星光电
文章目录 1、自我介绍2、介绍一下你最得意的一个项目3、这个项目里面都用到了什么模块,什么型号,有什么作用4、移植操作系统的过程中,流程是什么,需要注意什么移植操作系统的流程需要注意的事项面试官可能的追问及回答5、你用的传感器挺多的,怎么保证传感器传输的稳定性,…...
帮助,有奖提问
<?php $u $_GET[“user”]; //变量获取 $v $_GET[“variable”]; //$v看flag,绕过正则 $flag‘flag{}; if(isset($u)&&(file_get_contents($u,‘r’)“im admin”)){//猜测data://协议 //检查u指向 echo “hello admin!<br>”; if(preg_…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...
破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...
【实施指南】Android客户端HTTPS双向认证实施指南
🔐 一、所需准备材料 证书文件(6类核心文件) 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...
动态规划-1035.不相交的线-力扣(LeetCode)
一、题目解析 光看题目要求和例图,感觉这题好麻烦,直线不能相交啊,每个数字只属于一条连线啊等等,但我们结合题目所给的信息和例图的内容,这不就是最长公共子序列吗?,我们把最长公共子序列连线起…...
