Unix/C/C++--位域(位段)
1 介绍
有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。这在寄存器操作上用的较多些,如PLC行业modbus通信,位寄存器可能需要5个不需要一个字节。
2 结构体定义示例
typedef struct {
char name:6;
char city:2;
} DATA;
- :后面的数字用来限定成员变量占用的位数。
- C语言标准规定,位域的宽度不能超过它所依附的数据类型的长度。通俗地讲,成员变量都是有类型的,这个类型限制了成员变量的最大长度,:后面的数字不能超过这个长度。
3 支持类型
- C语言标准还规定,只有有限的几种数据类型可以用于位域。
- 在 ANSI C 中,这几种数据类型是 int、signed int 和 unsigned int(int 默认就是 signed int);
到了 C99,_Bool 也被支持了。 - 编译器在具体实现时都进行了扩展,额外支持了 char、signed char、unsigned char 以及 enum 类型,所以上面的代码虽然不符合C语言标准,但它依然能够被编译器支持。
4 位域存储
#include <stdio.h>
typedef struct {
char name:6;
char city:2;
} DATA;
int main()
{
DATA data;
printf("size of data: %ld\n", sizeof (data));
}
上述代码运行结果是1,结构体中位域成员紧邻存储占一个字节。
#include <stdio.h>
typedef struct {
int name:6;
int city:12;
int year:5;
} DATA;
int main(){
DATA data;
printf("size of data: %ld\n", sizeof (data));
}
上述代码运行结果是4,结构体中位域成员紧邻存储,共占23bit,之所以为 4,而不是 3,是因为要将内存对齐到 4 个字节,以便提高存取效率。
4.1 位域是否跨字节
#pragma pack(1)
// 叉车,2块IO板
typedef struct{
uint8_t io1InputL:8; //io板1,输入信号低8位,0~7
uint8_t io1InputH:4; //io板1,输入信号高4位,8~11
uint8_t io1Output:8; //io板1,输出信号 0~7
uint8_t io2InputL:8; //io板2,输入信号低8位,0~7
uint8_t io2InputH:4; //io板2,输入信号高4位,8~11
uint8_t io2Output:8; //io板2,输出信号 0~7
}LCD_IO_2_DATA;
其他结构体
#pragma pack()
sizeof(LCD_IO_2_DATA) windows下是6,linux是5,这说明编译不同,有的能跨字节有的不能跨字节。
#pragma pack(1)
// 叉车,2块IO板
typedef struct{
uint8_t io1InputL:8; //io板1,输入信号低8位,0~7
uint8_t io1InputH:4; //io板1,输入信号高4位,8~11
uint8_t io2InputH:4; //io板2,输入信号高4位,8~11
uint8_t io1Output:8; //io板1,输出信号 0~7
uint8_t io2InputL:8; //io板2,输入信号低8位,0~7
uint8_t io2Output:8; //io板2,输出信号 0~7
}LCD_IO_2_DATA;
其他结构体
#pragma pack()
sizeof(LCD_IO_2_DATA) windows下是5,linux是5
在某些实现中,位字段不能跨越变量边界。仅当变量的总位数符合该变量的数据类型时,才能在变量中定义多个位字段。
5 伪指令#pragma pack
· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。
参考
1、C语言位域(位段)详解
2、请问 C语言里的 结构体中定义变量 后面的:是什么意思
3、C - Why #pragma pack(1) Consider 6-bit struct member as an 8-bit?
4、字节对齐(强制对齐以及自然对齐)