关于内存对齐

一次开发遇到的内存对齐问题

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

你可能感兴趣的:(关于内存对齐)