美文网首页
关于内存对齐

关于内存对齐

作者: 树林里的小怪兽 | 来源:发表于2018-03-28 10:19 被阅读0次

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

    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 <iostream>
    #include <cstring>
    #include <cstdio>
    #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 <iostream>
    #include <cstring>
    #include <cstdio>
    #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

    相关文章

      网友评论

          本文标题:关于内存对齐

          本文链接:https://www.haomeiwen.com/subject/ccxycftx.html