【C语言】位段(结构体实现位段)

2024-06-04 04:12
文章标签 语言 实现 结构 位段

本文主要是介绍【C语言】位段(结构体实现位段),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、位段的定义

二、位段的声明

三、位段的内存分配

四、位段在内存中的存储方式

五、位段的优点

六、位段的跨平台问题

七、位段的应用

八、位段使用的注意事项


一、位段的定义

信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节。

例如:"真"或"假"可以用0或1表示,只需1位即可。这时我们就可以用位段来进行存储。

那么什么是位段呢?

        位段(Bit Field)是C语言中的一种数据结构,它允许程序员在一个结构体中以位为单位来指定其成员所占的内存长度。这种以位为单位的成员称为"位段""位域"

        位段的定义要借助于结构体,即以二进制位为单位定义结构体成员所占存储空间,从而可以按"位"来访问结构体中的成员。

        位段与结构体形式与用法上是很相近的,但位段可以用来描述更为细腻的数据级别。

二、位段的声明

位段的声明语法形式如下:

struct   标签

{

    位段成员类型 位段成员名:分配内存的大小;

}

举例:

struct A
{int _a : 2;  //分配2bit的空间大小int _b : 5;	 int _c : 10;int _d : 30;
};
//A就是一个位段类型
//int:位段的成员类型   _a: 位段成员名   2:分配内存的大小 

注意:

  1. 位段的成员必须是(整型):int,unsigned int 或signed int,char
  2. 位段的成员名后边有一个冒号和一个数字
  3. 位段成员名中的 "_" 是可以是可以省略的,加上下划线与不加都可以,只是一种命名风格。
  4. 位段中分配内存的大小,宽度必须小于等于指定类型的位宽度。(即:冒号后面的数字的bit不能超过前面类型所占的bit
  5. 位段的位指的是二进制位
  6. 位段的声明应在结构体/联合体中。

    原因:

    位段是依赖结构体/联合体来实现的。在位段的声明和使用中,虽然可以决定用多少位来存储数据,但不能认为位段就是可以自定义的数据类型。可以理解为:位段是依赖于结构体实现的自定义类型。可以认为位段是将一个盒子里面的格子自定义大小。

三、位段的内存分配

位段所占内存大小为多少呢?

我们测试下面一段代码:

struct S
{char _a : 3;char _b : 4;char _c : 5;char _d : 4;
};int main()
{printf("%d\n", sizeof(struct S));return 0;
}

测试结果如图所示:

为什么会是3个字节(byte)呢?

原因:

  1. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  2. 一个位段必须存储在同一存储单元(即字)之中,不能跨两个单元。如果其单元空间不够,则剩余空间不用,从下一个单元起存放该位段。

四、位段在内存中的存储方式

我们看如下一段代码:

#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;
}

调试结果如下:

我们调试发现:

10,12,3,4在内存中是以16进制存放,为什么是62 03 04呢?

分析如下:

可知:位段中的成员在内存中是从右向左分配。

注意:

  1. 大小端指的是如果一个数据存储时超过一个字节的时候,才有字节顺序。这里是一个字节(内部),所以不谈顺序。
  2. 这里是先开辟一个字节,再开辟一个字节,最后再开辟一个字节,所以存放顺序一定是如图所示的存放方式。

五、位段的优点

 可以使数据单元节省储存空间,避免不必要的空间浪费。

 但是所谓节省空间是在一定程度上节省空间,并不是完全不浪费。

六、位段的跨平台问题

        1、int被位段作为是:无符号整数还是有符号整数,这个并没有做出明确的规定。

        2、位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器就会出现问题)

        3、位段中的成员在内存中是从左向右分配,还是从右向左分配尚未定义。(vs中是从右向左)

        4、当一个结构包含两个位段,假设第二个位段成员无法全部容纳于第一个剩余的位时,是把一个空间填满再放到新开辟的空间,还是直接全部放到新的空间,这个没有明确的规定。(vs中是直接全部放到新的空间)

补充:

  • 只有在位段的时候,int是没有确定是使用unsigned还是signed。除此之外int都是signed int。
  •  而char才是在使用和不使用位段的时候都是不确定的。

总结:位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

七、位段的应用

        1、在一些特定的应用场景中,需要对一个整数类型的变量中的每一位进行单独的控制或访问。例如,硬件寄存器常包含一些特定的位用于表示设备的状态,配置选项或标志位。使用位段可以让程序员更方便地访问和控制这些位,不需要进行位运算。

        2、在网络协议中,IP数据报的格式。可以看到其中很多属性只需要几个bit位就能描述,使用位段就能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。

八、位段使用的注意事项

1、位段无地址,不能对位段进行取地址运算。

原因:

位段的几个成员共用一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。

因为内存中是每个字节分配一个地址,一个字节内部的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); //这是错误的//正确的示范int b = 0;scanf("%d", &b);sa._b = b;return 0;
}

2、位段里的成员类型要尽量保持一致。否则会带来没必要麻烦,它内存开辟的时候可能会跟期望的不一样。

原因:

位段使用的场景本来就非常苛刻。如果再类型不一样,这样写出来的代码可控性就会变得差,而且它有许多不确定性,导致了它的不跨平台性。

3、位段在访问时与结构体访问方式相同,通过点操作(.)进行访问。访问时注意不要超出了所定义的位段大小。

4、两位段相邻时,相同数据类型的位段在编译过程中可以提高存储效率,而不同数据类型的位段则更可能需要考虑数据对齐而降低存储效率。

这篇关于【C语言】位段(结构体实现位段)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1029030

相关文章

Spring IOC的三种实现方式详解

《SpringIOC的三种实现方式详解》:本文主要介绍SpringIOC的三种实现方式,在Spring框架中,IOC通过依赖注入来实现,而依赖注入主要有三种实现方式,构造器注入、Setter注入... 目录1. 构造器注入(Cons编程tructor Injection)2. Setter注入(Setter

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

Spring IOC控制反转的实现解析

《SpringIOC控制反转的实现解析》:本文主要介绍SpringIOC控制反转的实现,IOC是Spring的核心思想之一,它通过将对象的创建、依赖注入和生命周期管理交给容器来实现解耦,使开发者... 目录1. IOC的基本概念1.1 什么是IOC1.2 IOC与DI的关系2. IOC的设计目标3. IOC

Python实现文件下载、Cookie以及重定向的方法代码

《Python实现文件下载、Cookie以及重定向的方法代码》本文主要介绍了如何使用Python的requests模块进行网络请求操作,涵盖了从文件下载、Cookie处理到重定向与历史请求等多个方面,... 目录前言一、下载网络文件(一)基本步骤(二)分段下载大文件(三)常见问题二、requests模块处理

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言