电竞比分网-中国电竞赛事及体育赛事平台

分享

深入淺出變長結(jié)構(gòu)體

 zhouADNjj 2014-04-14

深入淺出變長結(jié)構(gòu)體

1、 問題的引出

        項(xiàng)目中用到數(shù)據(jù)包的處理,但包的大小是不固定的,其長度由包頭的2字節(jié)決定。比如如下的包頭:88 0f 0a ob cd ef 23 00 。長度由頭2個(gè)字節(jié)880f決定,考慮字節(jié)序,轉(zhuǎn)為0f88,轉(zhuǎn)為10進(jìn)制3976個(gè)字節(jié)的包長度。

        這個(gè)時(shí)候存儲包的時(shí)候,一方面可以考慮設(shè)定包的大小固定:如4K=4*1024=4096個(gè)字節(jié),因?yàn)樽畲蟀L不可能超過4k,但該方法的有缺陷,存在一種極端就是包最小僅含包頭不含數(shù)據(jù)域,此時(shí)包為8個(gè)字節(jié),浪費(fèi)了4096-8 =4088個(gè)字節(jié)的存儲空間。另一方面考慮有沒有一種方法能根據(jù)長度進(jìn)行存儲,或者說初始不分配長度,計(jì)算出了長度后再分配存儲呢。而實(shí)際項(xiàng)目中正是通過包頭計(jì)算出了包的整體大小的。

        這就引出了變長結(jié)構(gòu)體的概念。

 

2、 什么叫變長結(jié)構(gòu)體?

     如下所示:

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


        那結(jié)構(gòu)體是怎么實(shí)現(xiàn)可變長的呢?如上所示,請注意看結(jié)構(gòu)體中的最后一個(gè)元素,一個(gè)沒有元素的數(shù)組。我們可以通過動(dòng)態(tài)開辟一個(gè)比結(jié)構(gòu)體大的空間,然后讓buffer去指向那些額外的空間,這樣就可以實(shí)現(xiàn)可變長的結(jié)構(gòu)體了。更為巧妙的是,我們甚至可以nsize存儲字符串buffer的長度。

       并且,上述的結(jié)構(gòu)體可以擴(kuò)展,比如筆者項(xiàng)目中遇到的存儲數(shù)據(jù)包,前面可能類似包頭的部分(存儲類型、長度等信息),而后面buffer則存儲數(shù)據(jù)部分。

       同時(shí),需要引起注意的:ISO/IEC 9899-1999里面,這么寫是非法的,這個(gè)僅僅是GNU C的擴(kuò)展,gcc可以允許這一語法現(xiàn)象的存在。但最新的C/C++不知道是否可以,我沒有測試過。C99允許。

 

3、變長結(jié)構(gòu)體的好處體現(xiàn)在哪?

        可能有的同學(xué)會問到,1引出部分如果說定義定長數(shù)組浪費(fèi)空間,定義一個(gè)指針不也能指向變長的數(shù)據(jù)域部分嗎?

       是的,是可以實(shí)現(xiàn)的。那么我們就對比下有什么不同。

       結(jié)構(gòu)體1:s_one,用指針指向數(shù)據(jù)域部分;

       結(jié)構(gòu)體2:s_two, 用[0]的數(shù)組;

       結(jié)構(gòu)體3:s_three, 因?yàn)橛械木幾g器不支持[0],我們用[1]來表示;多了些存儲。

 

  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結(jié)構(gòu)體對齊  
  35. cout<< endl;  
  36.    
  37. //為buf分配100個(gè)字節(jié)大小的空間  
  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的編譯器會有如下的警告:

 

       運(yùn)行結(jié)果如下:

       對比結(jié)果,我們能發(fā)現(xiàn):

       <1> 存儲大小方面:s_two的存儲較s_one、s_three都要少,[0]的好處,即用指針的方式需要多開辟存儲空間的。

       <2> 數(shù)據(jù)連續(xù)存儲方面:s_one明顯數(shù)據(jù)域是單獨(dú)開辟的空間,與前的nsize不在連續(xù)的存儲區(qū)域,而s_two,s_three則在連續(xù)的存儲空間下。

       <3>釋放內(nèi)存方面:顯然s_one的指針的方式,需要先釋放數(shù)據(jù)域部分,才能釋放指向結(jié)構(gòu)體的指針變量;而s_two,s_three可以直接釋放。

       總結(jié)如下:

       結(jié)構(gòu)體最后使用0或1的長度數(shù)組的原因,主要是為了方便的管理內(nèi)存緩沖區(qū),如果你直接使用指針而不使用數(shù)組,那么,你在分配內(nèi)存緩沖區(qū)時(shí),就必須分配結(jié)構(gòu)體一次,然后再分配結(jié)構(gòu)體內(nèi)的指針一次,(而此時(shí)分配的內(nèi)存已經(jīng)與結(jié)構(gòu)體的內(nèi)存不連續(xù)了,所以要分別管理即申請和釋放)。

      而如果使用數(shù)組,那么只需要一次就可以全部分配出來,反過來,釋放時(shí)也是一樣,使用數(shù)組,一次釋放,使用指針,得先釋放結(jié)構(gòu)體內(nèi)的指針,再釋放結(jié)構(gòu)體。還不能顛倒次序。

      其實(shí)變長結(jié)構(gòu)體就是分配一段連續(xù)的的內(nèi)存,減少內(nèi)存的碎片化,簡化內(nèi)存的管理。

 

4、變長結(jié)構(gòu)體的應(yīng)用

       <1>Socket通信數(shù)據(jù)包的傳輸;

       <2>解析數(shù)據(jù)包,如筆者遇到的問題。

       <3>其他可以節(jié)省空間,連續(xù)存儲的地方等。

 

 

       未盡事宜,后續(xù)補(bǔ)上……

       2013/9/22pm21:36思于家中床前

 

作者:銘毅天下

轉(zhuǎn)載請標(biāo)明出處,原文地址:http://blog.csdn.net/laoyang360/article/details/11908731

如果感覺本文對您有幫助,請點(diǎn)擊支持一下,您的支持是我堅(jiān)持寫作最大的動(dòng)力,謝謝!

 

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多