结构体对齐引发的教训

结构体是对齐的,而且windows跟linux也不一样,平常不注意的话,会导致莫名其妙的问题

最近在找一个离奇的问题,最终虽然找到是强制转换导致问题,但实际上还是结构体对齐问题跟协议设计问题

 

背景:

某端口收到一帧数据,原始数据放在uint8_t *pBuf 里面

一般来说,为了解析方便点,要根据协议设计一个结构体,假设这个结构体名称为  ST_UNIR_BD

然后,用memcpy,将*pBuf里的数据,cp到 ST_UNIR_BD里,这样就完成了协议的解析,效率也比较高

 

以上是一个协议常规的一种做法

 

为了保密起见,后面的例子的内容,尽量贴近原来代码,但命名,业务肯定调整过的,不要太在意命名风格之类的。

先来看看,这个ST_UNIR_BD结构体的定义:

/*注释,命名都调整过。顺序跟协议里顺序是一致的*/
/*uin8_t=1字节,uint16是2字节,uint32是4字节*/
typedef struct
{
	uint8_t byIndex;
	uint8_t byModem;
	uint16_t wID;
	uint16_t wLast;	
	int32_t dnLocation;
	uint16_t wLimit;
	uint16_t wStation;
	uint32_t bB0_00 : 1;
	uint32_t bB0_01 : 1;
	uint32_t bB0_02 : 1;
	uint32_t bB0_03 : 1;
	uint32_t bB0_04 : 1;
	uint32_t bB0_05 : 1;
	uint32_t bB0_06 : 1;
	uint32_t bB0_07 : 1;
	uint32_t bB1_00 : 1;
	uint32_t bB1_01_05 : 5;
	uint32_t bB1_06 : 1;
	uint32_t bB1_07 : 1;
	uint32_t bB2_00_05 : 6;
	uint32_t bB2_06 : 1;
	uint32_t bB2_07 : 1;
	uint32_t bB3_00 : 1;
	uint32_t bB3_01 : 1;
	uint32_t bB3_02 : 1;
	uint32_t bB3_03 : 1;
	uint32_t bB3_04 : 1;
	uint32_t bB3_05 : 1;
	uint32_t bB3_06 : 1;
	uint32_t bB3_07 : 1;
	uint8_t byVer;
}ST_UNIR_BD;	

  

作为优秀的工程师,先不用代码sizeof,知道这个结构体的大小是多少吗?

 

ok。

是24字节

实际 sizeof也是24字节,实际有效字节是19字节,多出5个字节是冗余的

是我分析的:

 

 

这样一分析,实际上问题的原因也知道了。主要出现第二个4字节对齐上面,因为memcpy会导致对齐问题,导致后面的数据就会错位了

好在这个问题是“显”性的,往往细心一点,也能发现

 

如果,假设,是最后4字节对齐导致的话,可能比较恶心一点

 

那么如何避免此类问题产生?

1. 放弃不同类型强制转换,如有需要,自己写个函数。如果是C++的话,那写个构造函数

这样估计又有人要说了,这样代码效率低啊,低肯定低一些,但安全。关键还是安全

另外代码量也会多一点。

2. 设计协议时,应充分考虑对齐问题。比如,第2个4字节的地方,我们就考虑预留2字节,这样也可以避免。

这样做的好处,代码效率能够保证;缺点也很明显:有时候协议不是我们能够左右的;

如果要更改协议,代码扩展性较差,一旦疏忽了修改代码,往往会变成“隐”性问题,错位有时候也会功能正常的,

 

表现出来的现象:“我发的数据明明是对的!以前是好的啊!”

“有时候是好的,有时候不对” “不对的时候,数据很奇怪”

 

 

问题,总是恶心的。mark一下

 

posted @ 2021-02-09 11:05  小刚学长  阅读(389)  评论(0)    收藏  举报