C語言0數組/柔性數組使用介紹

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=前言:

上次看到一篇面試分享,裏面有個朋友說,面試官問了char[0] 相關問題,可是本身沒有遇到過,就繞過了這個問題。程序員

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

我本身在這篇文章下面作了一些回覆。watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=面試

如今我想結合我本身的理解,解釋一下這個 char[0] C語言柔性數組的問題。數組


0數組和柔性數組的介紹

0數組顧名思義,就是數組長度定義爲0,咱們通常知道數組長度定義至少爲1纔會給它分配實際的空間,而定義了0的數組是沒有任何空間,可是若是像上面的結構體同樣在最後一個成員定義爲零數組,雖然零數組沒有分配的空間,可是它能夠看成一個偏移量,由於數組名這個符號自己表明了一個不可修改的地址常量。柔性數組也叫可伸縮性數組,而0數組是一種柔性數組。數據結構

由於在早期沒引入0長度數組的時候, 你們是經過定長數組和指針的方式來解決的, 可是定長數組定義了一個足夠大的緩衝區, 這樣使用方便, 可是每次都形成空間的浪費指針的方式, 要求程序員在釋放空間是必須進行屢次的free操做, 而咱們在使用的過程當中每每在函數中返回了指向緩衝區的指針, 咱們並不能保證每一個人都理解並聽從咱們的釋放方式因此 GNU 就對其進行了0長度數組的擴展. 當使用data[0]的時候, 也就是0長度數組的時候,0長度數組做爲數組名, 並不佔用存儲空間。這樣就能夠更加高效的利用內存。tcp

在C99以後,也加了相似的擴展,只不過用的是 char payload[]這種形式(因此若是你在編譯的時候確實須要用到-pedantic參數,那麼你能夠將char payload[0]類型改爲char payload[], 這樣就能夠編譯經過了,固然你的編譯器必須支持C99標準的,若是太古老的編譯器,那可能不支持了。ide

0數組的常規使用

首先咱們定義一個結構體,再在一個結構體的最後,定義一個長度爲0的數組,就可使得這個結構體是可變長的。函數

以下所示:3d

//  0長度數組
struct zero_buffer
{
    int     len;
    char    data[0];
};

這個時候 data[0] 只是個數組名, 是不佔用存儲空間的.指針

這個結構體的大小用sizeof取長度,實際就是它的成員int的長度,data[0]不佔用空間。(數組名僅僅是一個符號, 它不會佔用任何空間, 它在結構體中, 只是表明了一個偏移量, 表明一個不可修改的地址常量!)blog

sizeof(struct zero_buffer) = sizeof(int)

printf("zero struct length is:%d int length is:%d\n",sizeof(struct zero_buffer),sizeof(int));

zero struct length is:4 int length is:4

對於0長數組的這個特色,很容易構造出咱們須要的數據結構,如緩衝區,數據包等等。

結構體定義如上所示


假設咱們須要設置一條tcp待發送的數據,長度是15,數據內容是"Hello My Friend",這樣咱們就能夠按照以下去定義了。其中  zbuffer->data 爲定義數據的地址,len表示數據的長度。

開闢空間以後使用

咱們使用的時候, 只須要開闢一次空間便可。

#define CURR_LENGTH 15
struct zero_buffer  *zbuffer = NULL;

//  開闢
if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL)
{
    zbuffer->len = CURR_LENGTH;
    memcpy(zbuffer->data, "Hello My Friend", CURR_LENGTH);
    printf("%d, %s\n", zbuffer->len, zbuffer->data);
}

使用完釋放空間

釋放空間一次釋放便可

//  銷燬
free(zbuffer);
zero_buffer = NULL;
其餘方法實現一些不定長數據的傳輸

除了0數組以外,還有使用定長數組和指針數組實現柔性數組的功能。

定長數組

定長數組顧名思義,就是在結構體裏面有個定長的數組,這個數組大小是按照咱們定義數據最大來進行設置的,爲了就是防止數據儲存的時候溢出。

定義

//  定長緩衝區
#define MAX_LENGTH      512
struct max_buffer
{
    int     len;
    char    data[MAX_LENGTH];
};

不過使用過程當中,好比我要發送 512 字節的數據, 若是用定長包, 假設定長包的最大長度 MAX_LENGTH 爲 1024, 那麼就會浪費 512 個字節的空間, 也會形成沒必要要的流量浪費。若是數組結構對齊放置(這塊知識詳細能夠看我以前的數據對齊的文章) sizeof(struct max_buffer) = sizeof(int)+ sizieof(char) * MAX_LENGTH

數據包的構造

通常來講, 咱們會返回一個指向緩衝區數據結構 max_buffer 的指針.

#define CURR_LENGTH 512
struct max_buffer   *mbuffer = NULL;
if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
{
    mbuffer->len = CURR_LENGTH;
    memcpy(mbuffer->data, "Hello World", CURR_LENGTH);
    printf("%d, %s\n", mbuffer->len, mbuffer->data);
}

前部分 4 個字節 p->len, 做爲包頭(就是多出來的那部分),這個包頭是用來描述緊接着包頭後面的數據部分的長度,這裏是 1024, 因此前四個字節賦值爲 1024 (既然咱們要構造不定長數據包,那麼這個包到底有多長呢,所以,咱們就必須經過一個變量來代表這個數據包的長度,這就是len的做用),

而緊接其後的內存是真正的數據部分, 經過 p->data, 最後, 進行一個 memcpy() 內存拷貝, 把要發送的數據填入到這段內存當中

釋放空間

當使用完畢釋放數據的空間的時候, 直接釋放就能夠了

free(mbuffer);
mbuffer = NULL;

使用定長數組, 做爲數據緩衝區, 爲了不形成緩衝區溢出, 數組的大小通常設爲足夠的空間 MAX_LENGTH, 而實際使用過程當中, 達到 MAX_LENGTH 長度的數據不多, 那麼多數狀況下, 緩衝區的大部分空間都是浪費掉的.

可是使用過程很簡單, 數據空間的開闢和釋放簡單, 無需程序員考慮額外的操做

指針數組

它和0數組的區別在於,零數組最後一個結構體元素定義一個data[0],而指針數組就是結構體中須要定義一個指針數組,這裏面的指針數組不須要特定在結構體的最後一個元素。

struct point_buffer
{
    char    *data;
    int     len;
};


考慮數組結構對齊(這塊知識詳細能夠看我以前的[數據對齊](https://mp.weixin.qq.com/s/35jJQy166-GgR9RaHafhog)的文章), 那麼數據結構的大小 sizeof(point_buffer)= sizeof(int) + (補齊int與char * 類型的長度值)+ sizeof(char * ),在個人64位編譯環境中int類型是4byte,char * 類型爲8byte,因此補齊的長度爲8-4,最終sizeof(point_buffer) 爲16byte。

若是結構體加上  _attribute((packed))  數據對齊修飾,則 sizeof(point_buffer)= sizeof(int)  sizeof(char * ),最終計算爲12byte。

空間分配使用

#define CURR_LENGTH 1024 
struct point_buffer *pbuffer = NULL;
if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
{
   pbuffer->len = CURR_LENGTH;
   if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL)
   {
       memcpy(pbuffer->data, "Hello World", CURR_LENGTH);


       printf("%d, %s\n", pbuffer->len, pbuffer->data);
   }
}

分配內存時,需採用兩步

首先, 需爲結構體分配一塊內存空間;

其次,再爲結構體中的成員變量分配內存空間.

這樣兩次分配的內存是不連續的, 須要分別對其進行管理. 當使用長度爲的數組時, 則是採用一次分配的原則, 一次性將所需的內存所有分配給它.

釋放

相反, 釋放時也是同樣的.

free(pbuffer->data);
free(pbuffer);
pbuffer = NULL;

使用指針結果做爲緩衝區, 只多使用了一個指針大小的空間, 無需使用固定長度的數組, 不會形成空間的大量浪費.

但那是開闢空間時, 須要額外開闢數據域的空間, 施放時候也須要顯示釋放數據域的空間, 可是實際使用過程當中, 每每在函數中開闢空間, 而後返回給使用者指向 struct point_buffer 的指針, 這時候咱們並不能假定使用者瞭解咱們開闢的細節, 並按照約定的操做釋放空間, 所以使用起來多有不便, 甚至形成內存泄漏

小結:

定長數組使用方便, 可是卻浪費空間, 指針形式只多使用了一個指針的空間, 不會形成大量空間分浪費, 可是使用起來須要屢次分配, 屢次釋放。因此最優解

0數組的優劣以及注意事項

優勢 :比起在結構體中聲明一個指針變量、再進行動態分配的辦法,這種方法效率要高。由於在訪問數組內容時,不須要間接訪問,避免了兩次訪存。此外,0數組也不會像定長數組會形成必定的內存的浪費。

缺點 :在結構體中,數組爲0的數組必須在最後聲明,使用上有必定限制。

結語

這就是我分享的零數組,若是你們有更好的想法和需求,也歡迎你們加我好友交流分享哈。

相關文章
相關標籤/搜索