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

共勉

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

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

相关文章

Leetcode面试经典150题-2.两数相加

解法都在代码里&#xff0c;不懂就留言或者私信 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val …

ubuntu插入模块测试

文章目录 一、环境二、步骤 一、环境 ubuntu 20.04 内核版本&#xff1a;5.15-generic 二、步骤 先看一下ubuntu用的哪个内核版本&#xff1a; 到内核目录下&#xff1a; 随便建个文件夹存一下编译完的ko模块&#xff1a; 写测试代码&#xff1a; 写makefile&#xff1a;…

Flink-DataWorks第六部分:数据运维(第62天)

系列文章目录 运维中心 4.1 功能概述 4.2 操作流程 4.2.1 操作流程概览 4.2.2 步骤一&#xff1a;查看周期任务配置 4.2.3 步骤二&#xff1a;测试周期任务 4.2.4 步骤三&#xff1a;周期任务补历史数据 4.2.5 步骤四&#xff1a;查看周期实例 4.2.6 步骤五&#xff1a;查看执…

knn图像分类

K近邻算法(K-NN)&#xff0c;即给定一个已训练的数据集&#xff0c;对新的输入实例&#xff0c;在训练数据集中找到与该实例最邻近的K个实例&#xff0c;这K个实例的多数属于某个类&#xff0c;则判定该输入实例同属此类。 1. OpenCV K近邻模块的使用 接下来通过一个例子&…

掌握 Nuxt 3 的页面元数据:使用 definePageMeta 进行自定义配置

title: 掌握 Nuxt 3 的页面元数据&#xff1a;使用 definePageMeta 进行自定义配置 date: 2024/8/11 updated: 2024/8/11 author: cmdragon excerpt: 摘要&#xff1a;本文详细介绍Nuxt 3框架中definePageMeta的使用方法&#xff0c;包括如何为页面组件定义元数据&#xff0…

集合的框架(之一)

集合的含义&#xff1a; 集合是一个可变的容器&#xff0c;可以随时向集合中添加元素&#xff0c;也可以随时从集合中删除元素。另外&#xff0c;集合还提供了若干个用来操作集合中数据的方法。集合里的数据&#xff0c;我们称之为元素(elements)&#xff1b;集合只能用来存储…

2025年美国数学竞赛AMC8暑期备考:吃透625道真题和知识点(持续)

距离接下来最近的2025年AMC8美国数学竞赛还有几个月的时间&#xff0c;实践证明&#xff0c;做真题&#xff0c;吃透真题和背后的知识点是备考AMC8有效的方法之一。 通过做真题&#xff0c;可以帮助孩子找到真实竞赛的感觉&#xff0c;而且更加贴近比赛的内容&#xff0c;可以…

1915_开源C语言实现的通用队列

经常在工作中遇到一些队列处理的场景&#xff0c;以前要么是借用FreeRTOS这样的系统中的相关功能&#xff0c;要么是通过数组做一个简单的队列模型。但是&#xff0c;这两种方案都具有一定的局限性能&#xff0c;前者要求的FreeRTOS不见得相应的软件中有&#xff0c;而后者只能…

超好玩的肉鸽游戏:《暴君的游戏》手机单机游戏分享

《暴君的游戏》&#xff08;Despots Game&#xff09;是一款结合了自走棋和roguelike元素的像素策略冒险游戏。游戏以其独特的战斗系统和丰富的职业选择&#xff0c;为玩家提供了深度的策略体验和探索乐趣。 游戏特色包括&#xff1a; 角色职业多样性&#xff1a;玩家可以招募…

使用历史版本比对法排查C++程序中的内存泄漏问题

目录 1、问题描述 2、使用Process Explorer实时查看程序的虚拟内存占用 2.1、对于内存泄漏问题,需要查看程序占用的虚拟内存 2.2、Windows任务管理器中看不到程序进程占用的虚拟内存,使用Process Explorer工具可以看到 2.3、通过Process Explorer工具看到每次泄漏的内存…

大数据-75 Kafka 高级特性 稳定性-一致性保证 LogAndOffset(LEO) HightWatermark(HW) 水位/水印

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

LVS实战项目

LVS简介 LVS:Linux Virtual Server&#xff0c;负载调度器&#xff0c;内核集成&#xff0c;章文嵩&#xff0c;阿里的四层SLB(Server LoadBalance)是基于LVSkeepalived实现。 LVS集群的类型 lvs-nat &#xff1a; 修改请求报文的目标IP, 多目标 IP 的 DNAT lvs-dr &#xff…

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——6Resnet实现黑线识别

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——6Resnet实现黑线识别 ​ 比赛还有重要部分就是黑线的识别&#xff0c;这块地平线社区的帖子很多 ​ 在本次我就使用了社区吴超大佬写出的文章&#xff0c;当然我们的步骤有所不同&#xff0c;也是比较省…

黄牛杀手 抢票脚本 V3.0

黄牛杀手 抢票脚本 V3.0 介绍 现在黄牛太tm多了&#xff0c;根本抢不到票 为了解决这个问题&#xff0c;开发了这个脚本&#xff0c;支持大麦网&#xff0c;淘票票、缤玩岛等多个平台 依赖 selenium (4.10.0以下版本) pip install selenium 现在黄牛太tm多了&#xff0c;根…

2.类和对象(上)

1. 类的定义 1.1 类定义格式 • class为定义类的关键字&#xff0c;Stack为类的名字&#xff0c;{ }中为类的主体&#xff0c;注意类定义结束时后面分号不能省略。类体中内容称为类的成员&#xff1a;类中的变量称为类的属性或成员变量; &#xff08;类和结构体非常像&#…

LVS原理——详细介绍

目录 lvs简介 LVS作用 LVS 的优势与不足 LVS概念与相关术语 LVS的3种工作模式 LVS调度算法 LVS-dr模式 LVS-tun模式 ipvsadm工具使用 lvs简介 LVS 是Linux Virtual Server的简称&#xff0c;也就是 Linux 虚拟服务器,是一个极好的负载均衡解决方案&#xff0c;它将一个…

计数排序,桶排序,基数排序

计数排序&#xff1a; 找出数据中的最大值和最小值&#xff0c;并创建哈希表&#xff0c;把 数据-最小值 作为数组的下标访问哈希表并标记数量&#xff0c;标记完后&#xff0c;遍历哈希表&#xff0c;当表中的值大于0&#xff0c;把 **下标最小值 (下标元素-最小值)**还原数据…

LLVM 寄存器分配

概述 基本寄存器分配器是四种寄存器分配器中最简单的寄存器分配pass实现(<llvm_root/livm/lib/CodeGen/RegAllocBasic.cpp>) 但麻雀虽小&#xff0c;五脏俱全&#xff0c;基本寄存器分配器中实现了根据溢出权重确实虚拟寄存器优先级、按优先级分配物理寄存器&#xff0…

韦东山瑞士军刀项目自学之UART

放自己一星期假回家&#xff0c;回来继续准备秋招。 本章记录关于UART协议的相关知识笔记。平时主要还是基于HAL库开发&#xff0c;但笔记里也讲了韦老师介绍的如何控制寄存器来设置UART的参数。 以及一些UART防止采集的抖动设置的一些策略与波特率与比特率的区别等。

文件共享服务NFS(服务名nfs,端口tcp/2049)

目录 前言 配置文件 工作原理 NFS服务器的配置 查看服务器是否安装 查看服务器状态 开启服务 编写配置文件 客户端挂载 前言 NFS&#xff08;Network File System&#xff09;是一种分布式文件系统协议&#xff0c;它允许网络中的不同计算机共享文件和目录&#xff0…