天天看點

深入淺出變長結構體

深入淺出變長結構體

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、 什麼叫變長結構體?

    如下所示:

struct Var_Len_Struct

{

    int nsize;

    char buffer[0];

};

       那結構體是怎麼實作可變長的呢?如上所示,請注意看結構體中的最後一個元素,一個沒有元素的數組。我們可以通過動态開辟一個比結構體大的空間,然後讓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]來表示;多了些存儲。

#include <stdafx.h>

#include <iostream>

using namespace std;

const int BUF_SIZE = 100;

struct s_one

ints_one_cnt;

char*s_one_buf;

struct s_two

ints_two_cnt;

chars_two_buf[0];

struct s_three

ints_three_cnt;

chars_three_buf[1];

int main()

//指派用

constchar* tmp_buf = "abcdefghijklmnopqrstuvwxyz";

intntmp_buf_size = strlen(tmp_buf);

//<1>注意s_one 與s_two的大小的不同

cout<< "sizeof(s_one) = " << sizeof(s_one) << endl; //8

cout<< "sizeof(s_two) = " << sizeof(s_two) << endl; //4

cout<< "sizeof(s_three) = " << sizeof(s_three) << endl;//5-->8結構體對齊

cout<< endl;

//為buf配置設定100個位元組大小的空間

intntotal_stwo_len = sizeof(s_two) + (1 + ntmp_buf_size) * sizeof(char);

intntotal_sthree_len = sizeof(s_three) + ntmp_buf_size * sizeof(char);

//給s_one buf指派

s_one*p_sone = (s_one*)malloc(sizeof(s_one));

memset(p_sone,0, sizeof(s_one));

p_sone->s_one_buf= (char*)malloc(1 + ntmp_buf_size);

memset(p_sone->s_one_buf,0, 1 + ntmp_buf_size);

memcpy(p_sone->s_one_buf,tmp_buf, ntmp_buf_size);

//給s_two buf指派

s_two*p_stwo = (s_two*)malloc(ntotal_stwo_len);

memset(p_stwo,0, ntotal_stwo_len);

memcpy((char*)(p_stwo->s_two_buf),tmp_buf, ntmp_buf_size);  //不用加偏移量,直接拷貝!

//給s_three_buf指派

s_three*p_sthree = (s_three*)malloc(ntotal_sthree_len);

memset(p_sthree,0, ntotal_sthree_len);

memcpy((char*)(p_sthree->s_three_buf),tmp_buf, ntmp_buf_size);

cout<< "p_sone->s_one_buf = " << p_sone->s_one_buf<< endl;

cout<< "p_stwo->s_two_buf = " << p_stwo->s_two_buf<< endl;

cout<< "p_sthree->s_three_buf = " <<p_sthree->s_three_buf << endl; //不用加偏移量,直接拷貝!

//<2>注意s_one 與s_two釋放的不同!

if(NULL != p_sone->s_one_buf)

       free(p_sone->s_one_buf);

       p_sone->s_one_buf= NULL;

       if(NULL != p_sone)

       {

              free(p_sone);

              p_sone= NULL;

       }

       cout<< "free(p_sone) successed!" << endl;

}

if(NULL != p_stwo)

       free(p_stwo);

       p_stwo= NULL;

       cout<< "free(p_stwo) successed!" << endl;

if(NULL != p_sthree)

       free(p_sthree);

       p_sthree= NULL;

       cout<< "free(p_sthree) successed!" << endl;

return0;

      筆者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>其他可以節省空間,連續存儲的地方等。

繼續閱讀