一次开发遇到的内存对齐问题
1.问题描述
在开发的过程中有一个协议头,结构如下:
{
char cHeadPad ; //消息头的标志 1
char cMessageFree ; //消息预留位 1 [客户端发来的为0 接管的为1]
int16_t nMessageSize ; //消息内容指令长度 2
int16_t nMessageOrder ; //消息指令序号 2
int16_t nMessageCmd ; //消息指令号 2
int16_t nMessageSCmd ; //消息子指令号 2
int32_t nMessageUid ; //用户数字ID 4
int32_t nMessageToken ; //用户令牌 4
char cMessageCheck ; //消息校验位 1
char cMessageEnd ; //消息头结束标识位 1
}STR_MSG_HEAD;
协议头的长度被定义为了 20 byte, 所以在接收到数据的时候直接使用如下代码进行填充
#define MSG_HEAD_LEN 20
STR_MSG_HEAD stHead;
memcpy(&(stHead), cMsgHead, MSG_HEAD_LEN );
在使用的过程中发现数据从nMessageUid 开始就是错误的,刚开始没有注意到内存对齐的问题,毕竟旧的代码没有问题,我重构的时候就错了.
问题分析
后来写了一个测试一步步分析,测试代码如下
#include
#include
#include
#define MSG_HEAD_LEN 20
using namespace std;
typedef struct _STR_MSG_HEAD //消息结构--头部
{
char cHeadPad ; //消息头的标志 1
char cMessageFree ; //消息预留位 1 [客户端发来的为0 接管的为1]
short int nMessageSize ; //消息内容指令长度 2
short int nMessageOrder ; //消息指令序号 2
short int nMessageCmd ; //消息指令号 2
short int nMessageSCmd ; //消息子指令号 2
int nMessageUid ; //用户数字ID 4
int nMessageToken ; //用户令牌 4
char cMessageCheck ; //消息校验位 1
char cMessageEnd ; //消息头结束标识位 1
}STR_MSG_HEAD;
char peer0_0[] = { /* Packet 29 */
0x08,
0x00,
0x4d, 0x00,
0x00, 0x00,
0x02, 0x00,
0x01, 0x00,
0x49, 0x89, 0xaa, 0x00,
0x49, 0x89, 0xaa, 0x00,
0x49,
0x08};
int main()
{
STR_MSG_HEAD head;
memcpy(&head, peer0_0, MSG_HEAD_LEN);
for (int i = 0; i < MSG_HEAD_LEN ; i++)
{
printf("%.2x ", (unsigned char)peer0_0[i]);
}
cout << endl;
cout << (head.cHeadPad&0xff) << endl;
cout << (head.cMessageFree&0xff) << endl;
cout << (head.nMessageSize) << endl;
cout << (head.nMessageOrder) << endl;
cout << (head.nMessageCmd) << endl;
cout << (head.nMessageSCmd) << endl;
cout << (head.nMessageUid) << endl;
cout << (head.nMessageToken) << endl;
cout << (head.cMessageCheck&0xff) << endl;
cout << (head.cMessageEnd&0xff) << endl;
return 0;
}
输出内容如下
08 00 4d 00 00 00 02 00 01 00 49 89 aa 00 49 89 aa 00 49 08
8
0
77
0
2
1
-1991704406
139002026
148
255
怀疑大小端问题
在先前的查问题过程中有怀疑到大小端对齐的问题,所以还特别的看了数据 里面head.nMessageToken
的值是139002026
转换为十六进制是0x84900aa
0xaa
是在peer0_0低地址中保存的,也就是说本机中低地址保存了数据的高位
协议头的数据如下
char peer0_0[] = { /* Packet 29 */
0x08,
0x00,
0x4d, 0x00,
0x00, 0x00,
0x02, 0x00,
0x01, 0x00,
0x49, 0x89, 0xaa, 0x00,
0x49, 0x89, 0xaa, 0x00,
0x49,
0x08};
大端存储数据
Big-Endian: 低地址存放高位,如下:
高地址
---------------
peer0_0[19] (0x08) -- 低位
peer0_0[18] (0x49)
peer0_0[17] (0x00)
peer0_0[16] (0xaa) -- 高位
低地址
如果是小端存储那么数据应该是这样的
Little-Endian: 低地址存放低位,如下:
高地址
---------------
peer0_0[19] (0xaa) -- 低位
peer0_0[18] (0x00)
peer0_0[17] (0x49)
peer0_0[16] (0x08) -- 高位
--------------
低地址
现在来看本机是大端存储的,同时从数据分析来看,head.nMessageToken
本应该对应的是0x49, 0x89, 0xaa, 0x00
, 现在看来是内存对齐的问题.
内存对齐问题
是旧的代码也是通过memcpy
函数直接赋值的。
查询资料了解到,在编译的时候可以控制结构体的内存对齐方式通过#pragma pack() 在代码中对指定的结构体控制内存对齐,
修改代码后测试
#include
#include
#include
#define MSG_HEAD_LEN 20
using namespace std;
#pragma pack(1) // 设为1字节对齐
typedef struct _STR_MSG_HEAD //消息结构--头部
{
char cHeadPad ; //消息头的标志 1
char cMessageFree ; //消息预留位 1 [客户端发来的为0 接管的为1]
short int nMessageSize ; //消息内容指令长度 2
short int nMessageOrder ; //消息指令序号 2
short int nMessageCmd ; //消息指令号 2
short int nMessageSCmd ; //消息子指令号 2
int nMessageUid ; //用户数字ID 4
int nMessageToken ; //用户令牌 4
char cMessageCheck ; //消息校验位 1
char cMessageEnd ; //消息头结束标识位 1
}STR_MSG_HEAD;
#pragma pack() // 恢复默认对齐方式
char peer0_0[] = { /* Packet 29 */
0x08,
0x00,
0x4d, 0x00,
0x00, 0x00,
0x02, 0x00,
0x01, 0x00,
0x49, 0x89, 0xaa, 0x00,
0x49, 0x89, 0xaa, 0x00,
0x49,
0x08};
int main()
{
STR_MSG_HEAD head;
cout << "STR_MSG_HEAD SIZE:" << sizeof(STR_MSG_HEAD) << endl;
memcpy(&head, peer0_0, MSG_HEAD_LEN);
for (int i = 0; i < MSG_HEAD_LEN ; i++)
{
printf("%.2x ", (unsigned char)peer0_0[i]);
}
cout << endl;
cout << (head.cHeadPad&0xff) << endl;
cout << (head.cMessageFree&0xff) << endl;
cout << (head.nMessageSize) << endl;
cout << (head.nMessageOrder) << endl;
cout << (head.nMessageCmd) << endl;
cout << (head.nMessageSCmd) << endl;
cout << (head.nMessageUid) << endl;
cout << (head.nMessageToken) << endl;
cout << (head.cMessageCheck&0xff) << endl;
cout << (head.cMessageEnd&0xff) << endl;
return 0;
}
现在问题已经解决,输出如下
STR_MSG_HEAD SIZE:20
08 00 4d 00 00 00 02 00 01 00 49 89 aa 00 49 89 aa 00 49 08
8
0
77
0
2
1
11176265
11176265
73
8
参考资料
#pragma pack -- 百度
大小端对齐 -- 百度
如何理解大小端对齐 -- 知乎
失传的C结构体打包技艺 -- gitHub