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

共勉

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小米里的大麦

您的支持是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值