位段——大项目中结构体节省空间之手段
学习目标:
位段是什么
位段的内存分配
位段的平台局限性和应用
学习内容:
1.位段是什么
C++中的位段(Bit fields)是一种用于有效利用内存的特性,可以在结构体中定义成员变量的位数。它允许我们将多个成员变量打包到同一个内存单元中,从而节省内存空间。
通过位段,我们可以指定结构体成员变量的位数,以及它们在内存中的存储顺序。比如,假设我们想要定义一个结构体来表示一个日期,其中包括年、月、日。在不使用位段的情况下,每个成员变量通常都会占用一个整型(比如int)的大小,即4个字节。但是如果我们知道年份的范围在0到99之间,月份在1到12之间,日期在1到31之间,那么我们可以使用位段来减小结构体的大小。
struct Date {
unsigned int year : 7; // 使用7位存储年份(0-99)
unsigned int month : 4; // 使用4位存储月份(1-12)
unsigned int day : 5; // 使用5位存储日期(1-31)
};
int main() {
Date d;
d.year = 21; // 存储的二进制为 010101,对应十进制为 21
d.month = 10; // 存储的二进制为 1010,对应十进制为 10
d.day = 18; // 存储的二进制为 10010,对应十进制为 18
// 输出结果
std::cout << "Year: " << d.year << std::endl;
std::cout << "Month: " << d.month << std::endl;
std::cout << "Day: " << d.day << std::endl;
std::cout<<sizeof(Data)<<std::endl;
return 0;
}
通过使用位段,我们可以将年份、月份和日期分别存储在一个字节内,总共只需要4个字节。这样可以有效地减小内存占用,但需要注意的是,位字段的位数必须小于或等于其所属成员变量类型大小的位数。此外,位字段还受限于平台和编译器的限制,对于不同的系统和编译器,行为可能会有所不同,因此在使用位段时要小心考虑跨平台和可移植性的问题。
(不同编译器不同,VS2022是4个字节,也有些是3个字节)
2.位段的内存分配——泾渭分明,军阀规定地盘
举个例子,刚刚开始是a:3个比特,b:4个比特,c:5个比特,d:4个比特。
出庄,论功行赏,多劳多得
a 出3,b出4,c出5,d出4
abcd四个人规划地盘(绿色空间),划分好了就互不侵犯了。
什么意思?几个意思?我来告诉你——一个开始不是开了三个的绿色的空间)吗?
(一个char型1字节,8比特位)
因为人家a是先定义声明的并且规定3个比特,意思是a先占据了第一个绿色空间的后3个位,后三个0的空间是他的。
然后b来了,因为你第一个绿色空间还有5个比特位,而且你b只需要4位,所以b必须要占第一个绿色空间的4-7位。那么c来了之后他需要5个比特位,怎么办?第一个绿色空间只剩下了一个比特位,所以c必须去第二个绿色空间后5位。最后按规则d去了第三个绿色空间后四位。
abcd入驻军队,如果前面人数不够就补0
最后规定好自己的领地之后互不侵犯,赋值的时候先把全部变量搞成0,再把10赋值给a,因为10的二进制01010——但是你只有3个位置,所以你只能拿后三位010放进去
b=12的(二进制01100)——位置够可以放进去(位数不够就可以前面可以补0,这个无伤大雅,二进制前面补0只要不超过32位,不改变符号位都无所谓的)。c和d以此类推。
> 最终二进制化为十六进制——十六进制(书面显示更紧凑)
拓展——十六进制好处
十六进制在计算机中的处理更加高效。一方面,内存地址在计算机中通常采用字节(8个比特)为单位,而十六进制正好可以充分利用每个字节的8个比特,因为每个十六进制位对应4个比特。另一方面,在二进制和十六进制之间进行转换的计算也相对简单和高效,可以通过位移和逻辑运算等操作来实现
3.位段跨平台的局限性和应用
C++位段(Bit fields)可以用于在结构体或类中定义成员变量的位数,从而节省内存空间,并且可以更高效地处理特定的位操作。下面是一些C++位段的应用举例:
1.压缩数据结构:
用于存储非常数范围的数据,但是数据范围相对较小且可预测的情况。例如,可以使用位段来存储标志位、状态值或记录控制信息,从而减小数据结构的占用空间。
struct FlagBits {
unsigned int isVisible : 1; // 1位用于表示是否可见
unsigned int isEditable : 1; // 1位用于表示是否可编辑
unsigned int isSelectable : 1; // 1位用于表示是否可选择
// 其他成员...
};
2.位操作和通信协议:
位段可以用于与底层硬件通信时,对数据进行位操作或者解析通信协议中的标识位。例如,可以使用位段来定义特定的位字段,以便对数据进行按位操作和提取。
struct Message {
unsigned int messageType : 4; // 4位用于表示消息类型
unsigned int payload : 8; // 8位用于表示有效载荷数据
// 其他成员...
};
3原始数据序列化:
位段可以用于将数据序列化为二进制,并且可以更加紧凑地表示数据。例如,可以使用位段将结构体中的各个字段压缩为指定的位数,然后将其传输或存储。
struct SensorData {
float temperature; // 温度
float humidity; // 湿度
unsigned int pressure : 14; // 14位用于表示压力值(范围在0-16383)
unsigned int status : 2; // 2位用于表示状态信息
// 其他成员...
};
后记:需要注意的是,位段的行为受编译器和平台的影响,可能在不同的系统和编译器上有所不同。此外,位段的使用也需要小心考虑可移植性和对齐问题,并且在处理位操作时需要注意位的顺序和对齐。因此,在使用位段时,建议查阅编译器文档以获取更详细的信息,并进行相应的测试和验证。