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

news2024/11/18 10:21:17

在这里插入图片描述

文章目录

  • 一、什么是位段
  • 二、位段的内存分配
    • 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.coloradmin.cn/o/2200473.html

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

相关文章

创建osd加入集群

故障原因&#xff1a;ceph节点一个磁盘损坏&#xff0c;其中osd69 down了&#xff0c;需要更换磁盘并重新创建osd加入ceph集群。 信息采集&#xff1a; 更换磁盘前&#xff0c;查询osd69对应的盘符&#xff1a; 将对应的故障磁盘更换后&#xff0c;并重做raid&#xff0c;然后查…

SDK4(note下)

以下代码涉及到了很多消息的处理&#xff0c;有些部分注释掉了&#xff0c;主要看代码 #include <windows.h> #include<tchar.h> #include <stdio.h> #include <strsafe.h> #include <string> #define IDM_OPEN 102 /*鼠标消息 * 键盘消息 * On…

76.【C语言】perror函数介绍

1.cplusplus的官网介绍 cplusplus的介绍 点我跳转 2.翻译 函数 perror void perror ( const char * str ); 打印错误信息 将errno(最后一个错误数字)的值解释为错误信息,之后把它打印到stderr中(标准错误输出流,通常是控制台)(备注有关"流"的概念在75.【C语言】文件…

k8s-pod的管理及优化设置

Pod是Kubernetes&#xff08;k8s&#xff09;中最小的资源管理组件&#xff0c;也是最小化运行容器化应用的资源对象。以下是对Pod的详细介绍&#xff1a; 一、Pod的基本概念 定义&#xff1a;Pod是Kubernetes中可以创建和管理的最小单元&#xff0c;是资源对象模型中由用户创…

网站排名,让网站快速有排名的几个方法

要让网站快速获得并提升排名&#xff0c;需要综合运用一系列专业策略和技术&#xff0c;这些策略涵盖了内容优化、技术调整、外链建设、用户体验提升等多个方面。以下是让网站快速有排名的几个方法&#xff1a; 1.内容为王&#xff1a;创造高质量、有价值的内容 -深入…

geolocator插件的用法

文章目录 1. 概念介绍2. 使用方法3. 示例代码4 体验分享我们在上一章回中介绍了如何实现滑动菜单相关的内容,本章回中将介绍如何获取位置信息.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在这里说的获取位置信息本质上是获取当前手机所在位置的gps坐标,就是我们…

【Chrome浏览器插件--资源嗅探猫抓】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、资源嗅探插件---猫抓二、使用步骤总结 一、资源嗅探插件—猫抓 猫抓是一个浏览器插件&#xff0c;可以检测当前网页中的一些资源文件&#xff0c;可设置嗅探的…

NVIDIA机密计算文档

NVIDIA 可信计算解决方案 文章目录 前言一、指南1. Intel TDX - 机密计算部署指南2. AMD SNP - 机密计算部署指南3. NVIDIA Hopper 机密计算证明文档4. nvtrust GitHub二、发行说明1. 550TRD3 - NVIDIA 可信计算解决方案发行说明2. 550TRD1 - NVIDIA 可信计算解决方案发行说明三…

The Android SDK location cannot be at the filesystem root

win11&#xff0c; 安装启动完Android Studio后&#xff0c;一直显示 The Android SDK location cannot be at the filesystem root因此需要下载SDK包&#xff0c;必须开启代理。 开启代理后&#xff0c;在System下开启自动检测代理&#xff0c;如图 重启Android Studio&a…

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

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

基于LORA的一主多从监测系统_0.96OLED

关联&#xff1a;0.96OLED hal硬件I2C LORA 在本项目中每个节点都使用oled来显示采集到的数据以及节点状态&#xff0c;OLED使用I2C接口与STM32连接&#xff0c;这个屏幕内部驱动IC为SSD1306&#xff0c;SSD1306作为从机地址为0x78 发送数据&#xff1a;起始…

Windows环境安装CentOS7

【注意】安装CentOS需要先安装Vmware虚拟机 【下载前准备】 一、下载CentOS 7镜像文件阿里云镜像开源&#xff0c;点击跳转 二、安装VMware&#xff08;17&#xff09;&#xff1a; a. 官网&#xff0c;点击跳转 b. 许可证&#xff1a;JU090-6039P-08409-8J0QH-2YR7F 安装V…

Aegisub字幕自动化及函数篇(图文教程附有gif动图展示)(二)

目录 template行 template pre-line template line template syl template syl noblank template char template notext template pre-line notext template syl noblank notext template keeptags ​编辑 template loop number 内联变量 ​编辑 remeber函数 re…

提示工程、微调和 RAG

自众多大型语言模型&#xff08;LLM&#xff09;和高级对话模型发布以来&#xff0c;人们已经运用了各种技术来从这些 AI 系统中提取所需的输出。其中一些方法会改变模型的行为来更好地贴近我们的期望&#xff0c;而另一些方法则侧重于增强我们查询 LLM 的方式&#xff0c;以提…

【华为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)…

CanOpen转Profinet网关与钢成型机等机械集成时发挥的作用

在现代工业自动化领域&#xff0c;不同设备和系统之间的通信至关重要。CanOpen和Profinet是两种广泛应用于工业控制系统的通讯协议。CanOpen通常用于设备级别的通信&#xff0c;而Profinet则更常见于工业以太网&#xff0c;适用于更大范围的系统级控制。当型钢成型机等复杂机械…

@Service代替@Controller注解来标注到控制层的场景?

在SpringBoot开发中&#xff0c;Controller和Service基本上是日常开发中使用的最频繁的两个注解。但你有没考虑过Service代替Controller注解来标注到控制层的场景&#xff1f;换言之&#xff0c;经过Service标注的控制层能否实现将用户请求分发到服务层的功能&#xff1f; 前言…

视频智能分析/AI智能分析网关V4客流统计算法介绍及其在多领域多场景中的应用

随着人工智能技术的快速发展&#xff0c;AI智能分析网关V4作为一种集高性能、低功耗于一体的软硬一体AI边缘计算硬件设备&#xff0c;在工地、工厂、园区、消防、社区、校园等领域展现出强大的应用潜力。本文将详细介绍AI智能分析网关V4的客流统计算法原理及其在多个场景中的应…

[Python] 函数入参中的 *args和**kwargs 是什么意思

Python函数中*args和**kwargs的用法&#xff0c;它们允许接收任意数量的位置参数和关键字参数。*args用于非关键字的可变参数&#xff0c;而**kwargs则用于接受键值对的可变参数。在调用函数时&#xff0c;字典参数需置于单实例参数之后。示例代码展示了如何定义和使用这些可变…

上海马拉松2024年:城市律动,跑者狂欢

随着2024年的脚步日益临近&#xff0c;上海这座国际化大都市即将迎来一场体育盛事——上海马拉松。作为城市律动与跑者狂欢的完美结合&#xff0c;上海马拉松不仅吸引了来自世界各地的跑者&#xff0c;更成为了展现上海城市魅力和体育精神的重要窗口。 本次上海马拉松将于2024年…