当前位置: 首页 > news >正文

【C语言】位段详解

🦄个人主页:小米里的大麦-CSDN博客

🎏所属专栏:https://blog.csdn.net/huangcancan666/category_12718530.html

🎁代码托管:黄灿灿 (huang-cancan-xbc) - Gitee.com

⚙️操作环境:Visual Studio 2022

目录

一、什么是位段?

二、位段的内存分配 

三、位段的跨平台问题

四、位段的应用

五、代码示例

六、位段的限制

七、位段与位域的区别

八、总结

共勉


一、什么是位段?

位段(Bit field)是一种数据结构,它允许你在单个整数变量中分配特定数量的位给不同的字段。这样做的目的是为了节省内存空间

  1. 位段(Bit field)的基本单位不是字节,而是位(bit)。位段是在单个整数类型变量中按照位来分配存储空间的一种数据结构。
  2. 位段的声明和结构是类似的,有两个不同:
  • 位段的成员必须是 int、unsigned int 或signed int 。
  • 位段的成员名后边有一个冒号和一个数字。

一个位段由多个成员组成,每个成员都有自己的名称和宽度(即占用的位数)。例如:

举个例子,假设你需要存储三个设置选项,每个选项只需要一两位来表示开启或关闭的状态。
如果用普通的整数变量来存储这些选项,每个整数至少会占用32位(如果是32位系统的话)。
但如果使用位段,你就可以只用三位来表示这三个选项,大大节省了空间。struct Settings {unsigned int option1 : 1;  // 占用1位unsigned int option2 : 1;  // 占用1位unsigned int option3 : 1;  // 占用1位
};

二、位段的内存分配 

  • 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  • 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

Settings 就是一个位段类型, 那位段 Settings 的大小是多少?一起来看看:

请注意

虽然位段是以位为单位,但实际存储这些位段的变量的大小通常是以字节为单位的。这是因为计算机内存是以字节为基本单元进行寻址的。例如,即使你的位段结构体只占用了8位(即1字节),由于内存对齐的要求,实际的结构体大小可能仍然是4字节。

总结一下:

  • 位段的基本单位:位(bit)
  • 位段存储的单位:字节(byte),但位段本身按位分配空间
  • 实际结构体的大小:通常以字节为单位,取决于编译器的内存对齐策略。

再看看这个:

#include <stdio.h>
struct A
{char _a : 3;char _b : 4;char _c : 5;char _d : 4;
};
int main()
{struct A a = {0};a._a = 10;a._b = 12;a._c = 3;a._d = 4;return 0;
}

假设:位段分配的内存中的比特位是从右向左使用的,分配剩余的bit位不够使用时,浪费掉剩余内存。则:

执行程序:a._a = 10; 10的二进制为1010,放入_a中,由于_a只有3bit,需要截断,所以舍弃最高位1,放入010:

执行程序:a._b = 12;,12的二进制为1100,刚好可以放入,如下图:

执行程序:a._c = 3;,3的二进制为11,由于_c有5bit,高位添0,放入00011,如下图:

执行程序:a._d = 4;,4的二进制为100,放入0100,如下图:

程序就基本执行完了,那么内存中是什么样的呢?根据上面分析,我们一开始给结构体初始化为0,我们可以得到:

也就是: 

由于机器是小端存储,所以内存上应该是:62 03 04.
经过调试,可以看到:

所以,位段的大小计算主要取决于你如何定义它以及编译器的具体实现。

struct BitField {unsigned int a: 3; // 占用3位unsigned int b: 5; // 占用5位unsigned int c: 1; // 占用1位
};
这里a占用了3位,b占用了5位,而c占用了1位。
理论上,这些位可以紧密排列在一起,但是实际的内存对齐规则可能会导致额外的空间被分配。a、b和c总共占用了9位。
如果使用32位的整数类型,则最终的位段结构可能会占用完整的32位,尽管实际上只使用了9位。所以,你可以使用sizeof运算符来确定位段的实际大小:
#include <stdio.h>int main() {struct BitField bitField;printf("Size of BitField: %zu bytes\n", sizeof(bitField));return 0;
}

要计算位段的实际大小,你需要考虑以下几点:

  • 位的总和计算所有成员位数之和。
  • 字边界对齐大多数编译器会按照字边界对齐原则来存储数据,这意味着即使位数总和小于一个字的基本单位(通常是8位或更常见的是32位),也会向上取整到下一个字的大小。
  • 编译器特定行为不同的编译器可能有不同的实现细节,包括如何处理跨越字边界的位字段。

三、位段的跨平台问题

跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。简略一点看:

  • int 位段被当成有符号数还是无符号数是不确定的。
  • 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  • 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  • 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

详细的看:

  1. 位字段的顺序:在某些平台上,位字段的顺序可能会影响其布局。例如,在一些系统中,位字段是从低到高排列的(从右到左),而在其他系统中则可能是从高到低排列的(从左到右)。

  2. 位字段的对齐:编译器可能会根据目标平台的内存对齐要求来对位字段进行对齐,这意味着位字段的实际布局可能会与预期不同。例如,在某些架构上,整数类型可能需要在特定的地址对齐(如4字节边界),这可能导致额外的空间被插入到位字段之间或之后。

  3. 位字段的大小:不同的编译器可能会有不同的默认整数类型大小。例如,int 类型在某些系统上可能是32位,在另一些系统上可能是64位。这会影响位字段的最大容量和布局。

  4. 位字段的填充:为了满足内存对齐的要求,编译器可能会在位字段之间添加填充位。例如,如果一个位字段在32位边界结束,而下一个位字段需要从新的32位边界开始,则编译器可能会在它们之间插入未使用的位。

  5. 位字段的访问:位字段的读写操作在不同的编译器和平台上可能会有所不同。有些编译器提供特定的操作符来访问位字段,而其他编译器可能需要使用位操作(如位移和按位与操作)来访问位字段。

为了避免位字段的跨平台问题,你可以采取以下措施:

  • 明确指定整数类型:使用 <stdint.h> 中定义的固定宽度整数类型(如 uint8_tuint16_tuint32_t 等),以确保位字段的大小在所有平台上都是一致的。
  • 手动管理对齐:如果需要严格的对齐控制,可以考虑手动在结构体中添加填充字段。
  • 避免依赖于位字段的特定布局:如果程序逻辑依赖于位字段的具体布局,那么在不同的平台上测试并验证行为是很重要的。
  • 使用位操作:使用位操作(如位移、按位与等)来访问位字段,这样可以确保代码在不同平台上的一致性。

四、位段的应用

位段(Bit field)在计算机科学和软件工程中有多种应用,特别是在需要高效存储和访问数据的情况下。通常用于处理那些只需要少量位的数据,比如状态标志、计数器等。通过使用位段,我们可以更有效地利用内存资源。 

此外,位段还可以用来模拟位数组。例如,如果我们想要表示一个有 8 个元素的布尔数组,可以使用一个字节来存储这个数组的所有元素:

struct {bool arr[8] : 1; // 每个元素占用1位
} bit_array;
这样,我们就用一个字节的空间实现了布尔数组的功能。

五、代码示例

下面是一些使用位段的例子:
// 示例1:定义一个表示颜色的位段
struct color {unsigned char red   : 5; // 红色部分占用5位unsigned char green : 6; // 绿色部分占用6位unsigned char blue  : 5; // 蓝色部分占用5位
};// 示例2:定义一个表示时间的位段
struct time {unsigned short hour   : 5; // 小时部分占用5位unsigned short minute : 6; // 分钟部分占用6位unsigned short second : 5; // 秒钟部分占用5位
};
当我们声明一个位段结构体变量时,我们可以同时初始化所有成员的值。例如:
struct color my_color = { .red = 0x1f, .green = 0x3f, .blue = 0x1f }; // 初始化颜色位段变量
struct time my_time = { .hour = 12, .minute = 30, .second = 0 };     // 初始化时间位段变量

六、位段的限制

虽然位段可以节省内存空间,但它也有一些限制:

  • 位段不能包含浮点数成员。
  • 位段不能包含字符串成员。
  • 位段不能包含结构体成员。
  • 位段不能包含联合体成员。

七、位段与位域的区别

位段和位域都是用来表示二进制位的结构体类型,但它们有一些区别:

  • 位段可以包含多个成员,而位域只有一个成员。
  • 位段的成员可以有不同的宽度,而位域的成员必须具有相同的宽度。
  • 位段的成员可以跨越字边界,而位域的成员不能跨越字边界。

八、总结

位段是 C 语言提供的一种非常有用的工具,可以帮助我们更高效地管理内存。但是,由于其跨平台性的问题,我们在使用位段时也需要谨慎考虑。

共勉

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 基于Android aosp系统的云手机chrome浏览器定制
  • 十三、代理模式
  • 八问八答搞懂Transformer内部运作原理
  • 贪心算法part03
  • 中场接杀放网前小球
  • VSCode 使用总结
  • HTTP状态码_五类
  • ComsolMatlab 螺旋孔多孔超材料高温吸声性能研究
  • 【微服务】Spring Cloud中如何使用Eureka
  • 在Visual Studio/Qt Creator 中使用CMake安装和使用vcpkg包
  • 全新在线客服系统源码(pc+h5+uniapp+公众号小程序+抖音)附搭建接入教程
  • 基于RK3568 Android11 移除长按电源按键弹窗的对话框中的 [关机] 和 [紧急呼救] 选项(详细分析)
  • Jenkins 部署Vue项目指引: Vue项目本地跨域代理 、解决ERR_UNSAFE_PORT
  • 轨迹优化 | 基于ESDF的共轭梯度优化算法(附ROS C++/Python仿真)
  • 驾驭企业数字化转型的利器:《TOGAF®标准第10版》
  • ----------
  • 【Leetcode】101. 对称二叉树
  • (三)从jvm层面了解线程的启动和停止
  • 【技术性】Search知识
  • Android Studio:GIT提交项目到远程仓库
  • Git学习与使用心得(1)—— 初始化
  • JavaScript DOM 10 - 滚动
  • Java超时控制的实现
  • Java基本数据类型之Number
  • LeetCode算法系列_0891_子序列宽度之和
  • MaxCompute访问TableStore(OTS) 数据
  • Objective-C 中关联引用的概念
  • October CMS - 快速入门 9 Images And Galleries
  • Shadow DOM 内部构造及如何构建独立组件
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • 二维平面内的碰撞检测【一】
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 一份游戏开发学习路线
  • #职场发展#其他
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (差分)胡桃爱原石
  • (分享)自己整理的一些简单awk实用语句
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (力扣题库)跳跃游戏II(c++)
  • (数据大屏)(Hadoop)基于SSM框架的学院校友管理系统的设计与实现+文档
  • (五)IO流之ByteArrayInput/OutputStream
  • (转)Sublime Text3配置Lua运行环境
  • ... fatal error LINK1120:1个无法解析的外部命令 的解决办法
  • .NET WPF 抖动动画
  • .Net程序帮助文档制作
  • .NET开发不可不知、不可不用的辅助类(一)
  • .NET是什么
  • .NET中统一的存储过程调用方法(收藏)
  • @Autowired和@Resource装配
  • [ 云计算 | Azure 实践 ] 在 Azure 门户中创建 VM 虚拟机并进行验证
  • [2008][note]腔内级联拉曼发射的,二极管泵浦多频调Q laser——
  • [AIGC 大数据基础]hive浅谈
  • [AIGC] HashMap的扩容与缩容:动态调整容量以提高性能
  • [BROADCASTING]tensor的扩散机制