Keil开发C51项目中C语言结构体位域问题

派派是只大橘喵
派派是只大橘喵
发布于 2023-12-10 / 114 阅读 / 0 评论 / 0 点赞

Keil开发C51项目中C语言结构体位域问题

 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上的这一篇文章之后,发现了问题,就是编译器在处理这种情况的时候是有出入的,不同的编译器结果可能不一样,下面是 这篇文章 关于 位域压缩 的一些规则。

  1. 宽度为 0 的一个未命名位域强制下一位域对齐到其下一type边界,其中type是该成员的类型。

  2. 位域的长度不能大于指定类型固有长度,比如说int的位域长度不能超过32,bool的位域长度不能超过8。

  3. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。

  4. 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止。

  5. 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍。

  6. 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式。

  7. 如果位域字段之间穿插着非位域字段,则不进行压缩。

  8. 不能对位段进行取地址操作。

  9. 若位段占的二进制位数为0,则这个位段必须是无名位段,下一个位段从下一个位段存储单元开始存放。

  10. 若位段出现在表达式中,则会自动进行整型升级,自动转换为int型或者unsigned int。

  11. 对位段赋值时,最好不要超过位段所能表示的最大范围,否则可能会造成意想不到的结果。

  12. 整个结构体的总大小为最宽基本类型成员大小的整数倍。

解决方案

根据上面的第六点

如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,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 ,编译器压缩了空间,问题解决。


评论