前言:今天看师傅的代码,我还以为是师傅写了个bug,兴致冲冲的改掉。结果细想之下发现我皮皮马就是个瓜皮,幸亏没跟师傅说他写了个bug,不然肯定得被笑死了。
一、事情发生始末
下面就先把涉及到的代码给大家看一下:
1 2 3 4 5 6 7 | typedef struct { Uint16 wSerialId; Uint16 wProtocolFlag; Uint16 wMsgLen; Uint8 byDevAddr; }TMDBusTcpHead;` |
这里定义了一个Modbus协议的消息头的结构体,里面4个数据共7个字节,但是由于编译器字节对齐的原因,结构体占8个字节(原因在其他博客中细谈)。
项目是收到Modbus协议数据_recvBuf进行解析,要将指针偏移到数据的协议头之后,也就是_recvBuf[7]这个位置。下面是我师傅写的地址偏移代码:
1 2 3 4 | char* NetIOMBusTcpImpl::getmsgBuf() { return &_recvBuf[sizeof(TMDBusTcpHead)]; } |
看到这段代码之后,我瞬间心想:“握草,我读书少不要骗我。编译器字节对齐sizeof(TMDBusTcpHead)不是8吗?”然后我就改掉了。这绝对是我师傅写的时候忘记了,绝对是这个原因(心中窃喜,嘿嘿嘿)。
冷静下来,有看了一遍代码,发现事情并没有这么简单,#pragma pack(push, 1)和#pragma pack(pop)这两行代码,我之前没注意(其实是没用过,不知道就自动忽略了)。然后去上网搜了一下它的用法,发现果然还是太年轻了,师傅就是师傅
-_-.
下面是#pragma pack(push,1)与#pragma pack(1)的用法与区别:
二、#pragma pack(push,1)与#pragma pack(1)的用法与区别:
1. 编译器默认字节对齐:
首先粗略讲一下编译器字节对齐的问题,比如上面的结构体:
1 2 3 4 5 6 7 | typedef struct { Uint16 wSerialId; Uint16 wProtocolFlag; Uint16 wMsgLen; Uint8 byDevAddr; }TMDBusTcpHead;` |
若不用#pragma pack(1)和#pragma pack()括起来,编译会按默认方式对齐(成员数据中的数据类型最大的对齐)。即按照Uint16(2字节对齐)
字节(Uint16)对齐,即sizeof(TMDBusTcpHead) = 8;
#pragma pack(push,1)与#pragma pack(1)的作用是给编译器用的参数设置,关于结构体内存的字节对齐,#pragma pack是指定数据在内存中的对齐方式。
2. #pragma pack (n) 与pragma pack(push,1)的区别
1 2 3 4 5 | #pragma pack(n) //作用:C编译器按照n个字节对齐 #pragma pack() //作用:取消自定义对齐方式 #pragma pack(push, 1) //作用:是指把原来对齐方式设置压栈,并设新的对齐方式设置为一个字节对齐 #pragma back(pop) //作用:恢复对齐状态 |
#pragma pack(push, 1)可能会比前者更优一点,但是二者其实也没有什么大的区别,比如:
1 2 3 | #prama pack(push) //保持字节对齐状态 #pragma pack(4) //设定为4字节对齐 相当于:#pragma pack (push,4) |