• 深入浅出变长结构体【转】


    深入浅出变长结构体

    1、 问题的引出

            项目中用到数据包的处理,但包的大小是不固定的,其长度由包头的2字节决定。比如如下的包头:88 0f 0a ob cd ef 23 00 。长度由头2个字节880f决定,考虑字节序,转为0f88,转为10进制3976个字节的包长度。

            这个时候存储包的时候,一方面可以考虑设定包的大小固定:如4K=4*1024=4096个字节,因为最大包长不可能超过4k,但该方法的有缺陷,存在一种极端就是包最小仅含包头不含数据域,此时包为8个字节,浪费了4096-8 =4088个字节的存储空间。另一方面考虑有没有一种方法能根据长度进行存储,或者说初始不分配长度,计算出了长度后再分配存储呢。而实际项目中正是通过包头计算出了包的整体大小的。

            这就引出了变长结构体的概念。

    2、 什么叫变长结构体?

         如下所示:

    [cpp] view plaincopy
     
    1. struct Var_Len_Struct  
    2. {  
    3.      int nsize;  
    4.      char buffer[0];  
    5. };  


            那结构体是怎么实现可变长的呢?如上所示,请注意看结构体中的最后一个元素,一个没有元素的数组。我们可以通过动态开辟一个比结构体大的空间,然后让buffer去指向那些额外的空间,这样就可以实现可变长的结构体了。更为巧妙的是,我们甚至可以nsize存储字符串buffer的长度

           并且,上述的结构体可以扩展,比如笔者项目中遇到的存储数据包,前面可能类似包头的部分(存储类型、长度等信息),而后面buffer则存储数据部分。

           同时,需要引起注意的:ISO/IEC 9899-1999里面,这么写是非法的,这个仅仅是GNU C的扩展,gcc可以允许这一语法现象的存在。但最新的C/C++不知道是否可以,我没有测试过。C99允许。

    3、变长结构体的好处体现在哪?

            可能有的同学会问到,1引出部分如果说定义定长数组浪费空间,定义一个指针不也能指向变长的数据域部分吗?

           是的,是可以实现的。那么我们就对比下有什么不同。

           结构体1:s_one,用指针指向数据域部分;

           结构体2:s_two, 用[0]的数组;

           结构体3:s_three, 因为有的编译器不支持[0],我们用[1]来表示;多了些存储。

    [cpp] view plaincopy
     
    1. #include <stdafx.h>  
    2. #include <iostream>  
    3. using namespace std;  
    4.    
    5. const int BUF_SIZE = 100;  
    6.    
    7. struct s_one  
    8. {  
    9. ints_one_cnt;  
    10. char*s_one_buf;  
    11. };  
    12.    
    13. struct s_two  
    14. {  
    15. ints_two_cnt;  
    16. chars_two_buf[0];  
    17. };  
    18.    
    19. struct s_three  
    20. {  
    21. ints_three_cnt;  
    22. chars_three_buf[1];  
    23. };  
    24.    
    25. int main()  
    26. {  
    27. //赋值用  
    28. constchar* tmp_buf = "abcdefghijklmnopqrstuvwxyz";  
    29. intntmp_buf_size = strlen(tmp_buf);  
    30.    
    31. //<1>注意s_one 与s_two的大小的不同  
    32. cout<< "sizeof(s_one) = " << sizeof(s_one) << endl; //8  
    33. cout<< "sizeof(s_two) = " << sizeof(s_two) << endl; //4  
    34. cout<< "sizeof(s_three) = " << sizeof(s_three) << endl;//5-->8结构体对齐  
    35. cout<< endl;  
    36.    
    37. //为buf分配100个字节大小的空间  
    38. intntotal_stwo_len = sizeof(s_two) + (1 + ntmp_buf_size) * sizeof(char);  
    39. intntotal_sthree_len = sizeof(s_three) + ntmp_buf_size * sizeof(char);  
    40.    
    41. //给s_one buf赋值  
    42. s_one*p_sone = (s_one*)malloc(sizeof(s_one));  
    43. memset(p_sone,0, sizeof(s_one));  
    44. p_sone->s_one_buf= (char*)malloc(1 + ntmp_buf_size);  
    45. memset(p_sone->s_one_buf,0, 1 + ntmp_buf_size);  
    46. memcpy(p_sone->s_one_buf,tmp_buf, ntmp_buf_size);  
    47.    
    48. //给s_two buf赋值  
    49. s_two*p_stwo = (s_two*)malloc(ntotal_stwo_len);  
    50. memset(p_stwo,0, ntotal_stwo_len);  
    51. memcpy((char*)(p_stwo->s_two_buf),tmp_buf, ntmp_buf_size);  //不用加偏移量,直接拷贝!  
    52.    
    53. //给s_three_buf赋值  
    54. s_three*p_sthree = (s_three*)malloc(ntotal_sthree_len);  
    55. memset(p_sthree,0, ntotal_sthree_len);  
    56. memcpy((char*)(p_sthree->s_three_buf),tmp_buf, ntmp_buf_size);  
    57.    
    58. cout<< "p_sone->s_one_buf = " << p_sone->s_one_buf<< endl;  
    59. cout<< "p_stwo->s_two_buf = " << p_stwo->s_two_buf<< endl;  
    60. cout<< "p_sthree->s_three_buf = " <<p_sthree->s_three_buf << endl; //不用加偏移量,直接拷贝!  
    61. cout<< endl;  
    62.    
    63. //<2>注意s_one 与s_two释放的不同!  
    64. if(NULL != p_sone->s_one_buf)  
    65. {  
    66.         free(p_sone->s_one_buf);  
    67.         p_sone->s_one_buf= NULL;  
    68.    
    69.         if(NULL != p_sone)  
    70.         {  
    71.                free(p_sone);  
    72.                p_sone= NULL;  
    73.         }  
    74.         cout<< "free(p_sone) successed!" << endl;  
    75. }  
    76.    
    77. if(NULL != p_stwo)  
    78. {  
    79.         free(p_stwo);  
    80.         p_stwo= NULL;  
    81.    
    82.         cout<< "free(p_stwo) successed!" << endl;  
    83. }  
    84.    
    85. if(NULL != p_sthree)  
    86. {  
    87.         free(p_sthree);  
    88.         p_sthree= NULL;  
    89.    
    90.         cout<< "free(p_sthree) successed!" << endl;  
    91. }  
    92.    
    93. return0;  
    94. }  


           笔者vc6.0的编译器会有如下的警告:

     

           运行结果如下:

           对比结果,我们能发现:

           <1> 存储大小方面:s_two的存储较s_one、s_three都要少,[0]的好处,即用指针的方式需要多开辟存储空间的。

           <2> 数据连续存储方面:s_one明显数据域是单独开辟的空间,与前的nsize不在连续的存储区域,而s_two,s_three则在连续的存储空间下。

           <3>释放内存方面:显然s_one的指针的方式,需要先释放数据域部分,才能释放指向结构体的指针变量;而s_two,s_three可以直接释放。

           总结如下:

           结构体最后使用0或1的长度数组的原因,主要是为了方便的管理内存缓冲区,如果你直接使用指针而不使用数组,那么,你在分配内存缓冲区时,就必须分配结构体一次,然后再分配结构体内的指针一次,(而此时分配的内存已经与结构体的内存不连续了,所以要分别管理即申请和释放)。

          而如果使用数组,那么只需要一次就可以全部分配出来,反过来,释放时也是一样,使用数组,一次释放,使用指针,得先释放结构体内的指针,再释放结构体。还不能颠倒次序。

          其实变长结构体就是分配一段连续的的内存,减少内存的碎片化,简化内存的管理

    4、变长结构体的应用

           <1>Socket通信数据包的传输;

           <2>解析数据包,如笔者遇到的问题。

           <3>其他可以节省空间,连续存储的地方等。

    您的资助是我最大的动力!
    金额随意,欢迎来赏!

  • 相关阅读:
    Android 开发技术周报 Issue#276
    手动添加 Git bash 到鼠标右键
    Sublime Text3 3143 注册码
    MySQL主从复制
    Python-MongoDB的驱动安装、升级
    MongoDB数据库的安装、配置和使用
    js循环生成多个easyui datagrid数据网格时,初始化表格
    屏蔽掉Google Chrome 浏览器 textarea 单词拼写检测
    Fiddler屏蔽某些url的抓取方法
    解决VMware下安装Ubuntu 16.04 不支持1920X1080分辨率的问题
  • 原文地址:https://www.cnblogs.com/Corphish/p/9225066.html
Copyright © 2020-2023  润新知