结构体对齐引发的教训
结构体是对齐的,而且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一下