Keil开发C51项目中C语言结构体位域问题
今天在Keil中调试一个C51项目,其中一个头文件中有一个结构体使用了位域,但是sizeof(结构体)和预期的不一样。然后去查一下资料发现里面还是有点东西,需要记录一下。
问题
下面就是这个结构体
typedef struct ZKey_S{
KeyType key_type;
UINT8 para_0;
UINT8 para_1;
UINT8 para_2;
UINT8 para_3 : 4;
UINT16 loop_time : 12; //loop time;
}S_ZKey;
说明一下 : KeyType 是一个枚举类型
/*********************Universal enums********************/
typedef enum KeyType{
NORMAL = 0x80,
PDPU_MASK = 0x40, //press down or press up mask
PRESS_DOWN = 0x41, //
PRESS_UP = 0x42,
COMPOUND_MASK = 0x20,
CLICK = 0x21,
DOUBLE_CLICK = 0x22,
HOLD = 0x24,
SINGLETON_MASK = 0x10,
FN = 0x11,
ACTION = 0x12,
AUTO = 0x13,
LOCK = 0x14,
DUL = 0x15
}KeyType;
/*******************************************************/
可以看到KeyType在结构体内是只占一个字节的(8位C51单片机的字长为1B)
那么可以计算出 这个结构体的总长度
KeyType key_type; //8位 1字节
UINT8 para_0; //8位 1字节
UINT8 para_1; //8位 1字节
UINT8 para_2; //8位 1字节
UINT8 para_3 : 4; //4位
UINT16 loop_time : 12; //12位 和前面para_3加起来 2字节
总长度 = 1 + 1 + 1 + 1 + 2 = 6字节。
但是我们sizeof(S_ZKey) 确确实实 = 7,为什么会这样呢?
查阅资料
看了CSDN上的这一篇文章之后,发现了问题,就是编译器在处理这种情况的时候是有出入的,不同的编译器结果可能不一样,下面是 这篇文章 关于 位域 和 压缩 的一些规则。
宽度为 0 的一个未命名位域强制下一位域对齐到其下一type边界,其中type是该成员的类型。
位域的长度不能大于指定类型固有长度,比如说int的位域长度不能超过32,bool的位域长度不能超过8。
位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。
如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止。
如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍。
如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式。
如果位域字段之间穿插着非位域字段,则不进行压缩。
不能对位段进行取地址操作。
若位段占的二进制位数为0,则这个位段必须是无名位段,下一个位段从下一个位段存储单元开始存放。
若位段出现在表达式中,则会自动进行整型升级,自动转换为int型或者unsigned int。
对位段赋值时,最好不要超过位段所能表示的最大范围,否则可能会造成意想不到的结果。
整个结构体的总大小为最宽基本类型成员大小的整数倍。
解决方案
根据上面的第六点
如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式。
可以发现,在示例结构体中,para_3 的类型为 UINT8 但是 loop_time 的类型为 UINT16 , 这是两个不同的类型,在Keil的编译器中实现不同,应该是采取了不压缩的方式,导致结构体长度发生了以下变化:
//此为KEIL中的情况
KeyType key_type; //8位 1字节
UINT8 para_0; //8位 1字节
UINT8 para_1; //8位 1字节
UINT8 para_2; //8位 1字节
UINT8 para_3 : 4; //4位 1字节(因为和下一个成员类型不同,Keil不采取压缩,那么变为1字节)
UINT16 loop_time : 12; //12位 2字节(因为和上一个成员类型不同,Keil不采取压缩,该成员长度为16位也就是2字节)
如果要启用压缩的话,只需要把两个成员的类型改为同一种就行
typedef struct ZKey_S{
KeyType key_type;
UINT8 para_0;
UINT8 para_1;
UINT8 para_2;
UINT16 para_3 : 4; //注意这里的类型从 UINT8 改为了 UINT16 位了和下一个成员统一类型
UINT16 loop_time : 12;
}S_ZKey;
随后 sizeof(S_ZKey) == 6 ,编译器压缩了空间,问题解决。