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

共勉

相关文章:

【C语言】位段详解

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:https://blog.csdn.net/huangcancan666/category_12718530.html &#x1f381;代码托管:黄灿灿 (huang-cancan-xbc) - Gitee.com ⚙️操作环境:Visual Studio 2022 目录 一、什么是位段&#xff1f; 二、…...

LVS集群实验

NAT模式 本质是多目标IP的DNAT&#xff0c;通过将请求报文中的目标地址和目标端口修改为某挑出的RS的RIP和PORT实现转发RIP和DIP应在同一个IP网络&#xff0c;且应使用私网地址:RS的网关要指向DIP请求报文和响应报文都必须经由Direclor转发&#xff0c;Direclor易于成为系统瓶…...

在 Spring Boot 中使用适配器模式实现支付网关的统一接口

引言 在许多电子商务系统中&#xff0c;集成多个支付网关是常见的需求。不同的支付网关有着不同的接口和实现细节。适配器模式可以帮助我们以一种灵活的方式实现这些不同的支付网关接口。 适配器模式简介 适配器模式将一个类的接口转换为客户期望的另一个接口。适配器模式使…...

【书生·浦语大模型实战营】第三期 入门岛作业

入门岛作业 Linux闯关任务&#xff1a;完成 SSH 连接与端口映射并运行 hello_world.py。配置vscode作业内容 可选任务1&#xff1a;将Linux基础命令在开发机上完成一遍作业内容 可选任务 2&#xff1a;使用 VSCODE 远程连接开发机并创建一个conda环境作业内容 可选任务 3&#…...

Redis的String类型常用命令总结

1. set 设置一个键的值。 set key value示例&#xff1a; set username "alice"2. get 获取一个键的值。 get key示例&#xff1a; get username3. getset 设置键的值&#xff0c;并返回键的旧值。 getset key value示例&#xff1a; getset username "…...

河南萌新联赛2024第(四)场:河南理工大学

A 思路&#xff1a; B 思路&#xff1a;有一种贪心的写法&#xff0c;将整个数组排序以后比较两个相邻数的同或值&#xff0c;取 m a x max max&#xff0c;不会证明 int th(int x, int y, int z) {int res 0;for (int i z - 1; i > 0; i --) {int dx (x >> i &…...

Linux中临时使用账号提权进行业务操作

普通账号提权 su&#xff1a;永久提权 Switching users with su sudo&#xff1a;临时提权 Running commands as root with sudo 1&#xff09;su切换账号 需要对方的密码 示例&#xff1a;切换到超级管理员 su - root 建议用-这个 su root 普通用户需要执行特殊指令&…...

lwip 3. 网线拔掉后 lwip_recvfrom不能返回

当网线被拔掉后&#xff0c;‌LWIP的lwip_recvfrom函数无法返回&#xff0c;‌这通常意味着网络连接已经断开&#xff0c;‌而LWIP没有自动检测到这种断开并进行相应的处理。具体卡在这个地方&#xff1a; ret xQueueReceive(mbox->mbx, &(*msg), portMAX_DELAY); //具…...

Linux环境安装Docker Engine

Docker是一个开源的应用容器引擎&#xff0c;由Go语言开发&#xff0c;基于Linux内核技术。Docker通过将应用及其依赖打包到可移植的容器中&#xff0c;实现了应用的快速部署和高效管理。Docker容器具有轻量级、快速启动、可移植性强等特点&#xff0c;能够显著提升资源利用率和…...

大厂面试题分享

大厂面试题分享 Redis持久化方式AOF优缺点RDB优缺点 如何保证Redis和Myql的一致性索引下推输入url到浏览器发生了什么ReentranLock底层原理SpringBoot 的启动流程 Redis持久化方式 Redis提供了两种主要的持久化机制&#xff0c;分别是AOF&#xff08;Append-Only File&#xf…...

FPGA面试问题整理

1. 逻辑设计中竞争与冒险概念&#xff0c;如何识别和消除&#xff1f; 竞争&#xff1a;在组合逻辑电路中&#xff0c;信号经过多条路径到达输出端&#xff0c;每条路径经过的逻辑门不同存在时差&#xff0c;在信号变化的瞬间存在先后顺序。这种现象叫竞争。 冒险&#xff1a;由…...

3Done学习笔记

一、基本操作 1、旋转视角 使用左下角立方体选择&#xff1b; 右键可以拖动视角&#xff1b; 中间滑轮按住拖动整个舞台界面。 2、平移和旋转 右键选择移动&#xff0c;有两种方式。 第一种选择起始点&#xff0c;按照起始点位置移动到终止点&#xff08;边、角、中心点…...

AI学习指南深度学习篇-卷积层详解

AI学习指南深度学习篇-卷积层详解 一、引言 随着人工智能技术的不断发展&#xff0c;深度学习作为人工智能领域的热门分支之一&#xff0c;正在逐渐成为各个领域的核心技术。而在深度学习中&#xff0c;卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN…...

2024年TI杯E题-三子棋游戏装置方案分享-jdk123团队-第二弹 手搓机械臂

第一弹赛题的选择与前期方案的准备 opencv调用摄像头bug的解决 机械臂的组装 采用三个舵机&#xff0c;组成一个三自由度的机械臂。 并且利用电磁吸盘的方式&#xff0c;完成对棋子的抓取工作&#xff0c;后面的事实证明&#xff0c;在预算不足的情况下&#xff0c;队友手搓…...

如何在Java、C、Ruby语言中使用Newscatcher API

Newscatcher 世界实时新闻聚合API 一款强大的数据服务工具&#xff0c;它通过先进的网络爬虫技术&#xff0c;实时从全球超过70,000个新闻源聚合新闻内容。这个API能够提供全面、多角度的新闻报道&#xff0c;包括但不限于标题、作者、发布日期、全文内容以及媒体资源链接。它使…...

集合: Collection的成员方法和相关实现类

Collection: - List(有序【指的是存储和取出的顺序是一致的】且可以发生重复&#xff0c;且有索引的概念) - ArrayList&#xff1a; 底层数据结构是数组&#xff0c;查询快&#xff0c;增删慢&#xff0c;线程不安全的&#xff0c;效率高。 - …...

过滤器与监听器:深入了解 Java Web 开发中的核心概念

在 Java Web 开发中&#xff0c;过滤器&#xff08;Filter&#xff09;和监听器&#xff08;Listener&#xff09;是两个重要的组件&#xff0c;它们帮助开发者在请求处理的各个阶段进行预处理和后处理。这篇博客将深入探讨这两个概念&#xff0c;并展示它们如何在实际应用中发…...

【Linux学习】动静态库从原理到制作

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 &#x1f351;动静态库&#x1f41f;动静态库的制作与使用&#x1f680;生成静态库&#x1f512;生成动态库 &#x1f98c;动态库的查…...

WPF篇(10)-Label标签+TextBlock文字块+TextBox文本框+RichTextBox富文本框

Label标签 Label控件继承于ContentControl控件&#xff0c;它是一个文本标签&#xff0c;如果您想修改它的标签内容&#xff0c;请设置Content属性。我们曾提过ContentControl的Content属性是object类型&#xff0c;意味着Label的Content也是可以设置为任意的引用类型的。 案…...

JavaFX对话框控件-ChoiceDialog

JavaFX对话框控件-ChoiceDialog 常用属性titlecontentTextinitOwnergraphicheaderTextdefaultValuechoicesdialogPane 常用事件显示事件setOnShowing显示事件setOnShown弹框按钮点击 综合案例自定义下拉框内容 与Alert大部分功能类似按钮不可以自定义多一个下拉框 常用属性 …...

一文了解BTC中的二层协议中Nervos network,CKB,RGB++,UTXO stack 之间的关系

注&#xff1a;该内容不构成投资建议&#xff0c;有些内容摘抄其他地方&#xff0c;如侵权&#xff0c;请联系删除。 Nervos network Nervos Network 是一个开源的区块链生态项目&#xff0c;该项目提供一套解决方案来应对区块链扩展性和互操作性的问题。 Nervos Network 成立…...

Oracle(47)如何创建和使用集合?

在PL/SQL中&#xff0c;集合&#xff08;Collection&#xff09;是一种复合数据类型&#xff0c;用于存储一组相关的数据项。集合主要有三种类型&#xff1a;关联数组&#xff08;Associative Arrays&#xff09;、嵌套表&#xff08;Nested Tables&#xff09;和可变数组&…...

SpringIOC和SpringAOC

lombok插件 XML<!-- 加载资源文件 --><context:property-placeholder location"classpath:jdbc.properties"></context:property-placeholder><!-- 注入数据源 --><bean id"dataSource" class"com.mchange.v2.c3p0.ComboP…...

static关键字详解

文章目录 static使用示例static底层原理静态初始化顺序静态与线程安全 static static是Java中的一个关键字&#xff0c;用于定义类级别的成员&#xff0c;类级别的成员是指那些属于整个类&#xff0c;而不是特定对象实例的成员。在Java中&#xff0c;类级别的成员包括静态变量…...

使用 Java RestClient 与 Elasticsearch 进行索引管理的示例

文章目录 准备工作测试连接创建索引查询索引是否存在删除索引总结 在这篇博客中&#xff0c;我将和大家分享如何使用 Java RestClient 与 Elasticsearch 进行简单的索引管理操作。如果你在开发过程中需要对海量数据进行高效搜索和分析&#xff0c;Elasticsearch 可能是个不错的…...

编程-设计模式 10:外观模式

设计模式 10&#xff1a;外观模式 定义与目的 定义&#xff1a;外观模式&#xff08;Facade Pattern&#xff09;提供了一个统一的接口&#xff0c;用来访问子系统中的一群接口。它定义了一个高层接口&#xff0c;让子系统更容易使用。目的&#xff1a;简化复杂的子系统的使用…...

非范型ArrayList和泛型List<T>

ArrayList 是 C# 中的一个非泛型集合类&#xff0c;它属于 System.Collections 命名空间。它提供了动态数组的功能&#xff0c;允许你在运行时添加、删除和访问元素。然而&#xff0c;需要注意的是&#xff0c;ArrayList 并不是类型安全的&#xff0c;因为你可以向其中添加任何…...

魔众文库系统v7.0.0版本推荐店铺功能,管理菜单逻辑优化

推荐店铺功能&#xff0c;管理菜单逻辑优化 [新功能] RandomImageProvider 逻辑升级重构&#xff0c;支持更丰富的随机图片生成 [新功能] 资源篮订单参数字段 [新功能] 首页推荐店铺功能&#xff0c;需要在后台 文库系统 → 文库店铺 开启推荐 [系统优化] Grid 快捷编辑请求…...

03、流程控制语句

01、位运算符 一、位运算符:是针对二进制数据(补码)的运算。(0看成false&#xff0c;1看成true) &:按位与——求出两个数字对应的二进制&#xff0c;有0则0 | :按位或——求出两个数字对应的二进制&#xff0c;有1则1 ^ :按位异或 ——求出两个数字对应的二进制&#xff0c…...

[Android] [解决]Bottom Navigation Views Activity工程带来的fragment底部遮盖的问题

创建了Bottom Navigation Views Activity之后&#xff0c;在fragment_home.xml&#xff0c;加了一个RecyclerView&#xff0c; 后来添加了item之后发现底部会被盖住一部分。 解决&#xff1a;在layout里面加两句&#xff1a; android:paddingBottom"?attr/actionBarSize&…...