C語言0數(shù)組/柔性數(shù)組使用介紹
前言:
上次看到一篇面試分享,里面有個朋友說,面試官問了char[0] 相關問題,但是自己沒有遇到過,就繞過了這個問題。

我自己在這篇文章下面做了一些回復。
現(xiàn)在我想結合我自己的理解,解釋一下這個 char[0] C語言柔性數(shù)組的問題。
作者:良知猶存
轉載授權以及圍觀:歡迎關注微信公眾號:羽林君
或者添加作者個人微信:become_me
0數(shù)組和柔性數(shù)組的介紹
0數(shù)組顧名思義,就是數(shù)組長度定義為0,我們一般知道數(shù)組長度定義至少為1才會給它分配實際的空間,而定義了0的數(shù)組是沒有任何空間,但是如果像上面的結構體一樣在最后一個成員定義為零數(shù)組,雖然零數(shù)組沒有分配的空間,但是它可以當作一個偏移量,因為數(shù)組名這個符號本身代表了一個不可修改的地址常量。柔性數(shù)組也叫可伸縮性數(shù)組,而0數(shù)組是一種柔性數(shù)組。
因為在早期沒引入0長度數(shù)組的時候, 大家是通過定長數(shù)組和指針的方式來解決的, 但是定長數(shù)組定義了一個足夠大的緩沖區(qū), 這樣使用方便, 但是每次都造成空間的浪費指針的方式, 要求程序員在釋放空間是必須進行多次的free操作, 而我們在使用的過程中往往在函數(shù)中返回了指向緩沖區(qū)的指針, 我們并不能保證每個人都理解并遵從我們的釋放方式所以 GNU 就對其進行了0長度數(shù)組的擴展. 當使用data[0]的時候, 也就是0長度數(shù)組的時候,0長度數(shù)組作為數(shù)組名, 并不占用存儲空間。這樣就可以更加高效的利用內存。
在C99之后,也加了類似的擴展,只不過用的是 char payload[]這種形式(所以如果你在編譯的時候確實需要用到-pedantic參數(shù),那么你可以將char payload[0]類型改成char payload[], 這樣就可以編譯通過了,當然你的編譯器必須支持C99標準的,如果太古老的編譯器,那可能不支持了。
0數(shù)組的常規(guī)使用
首先我們定義一個結構體,再在一個結構體的最后,定義一個長度為0的數(shù)組,就可以使得這個結構體是可變長的。
如下所示:
// 0長度數(shù)組
struct zero_buffer
{
int len;
char data[0];
};
這個時候 data[0] 只是個數(shù)組名, 是不占用存儲空間的.
這個結構體的大小用sizeof取長度,實際就是它的成員int的長度,data[0]不占用空間。(數(shù)組名僅僅是一個符號, 它不會占用任何空間, 它在結構體中, 只是代表了一個偏移量, 代表一個不可修改的地址常量!)
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長數(shù)組的這個特點,很容易構造出我們需要的數(shù)據(jù)結構,如緩沖區(qū),數(shù)據(jù)包等等。
結構體定義如上所示
假設我們需要設置一條tcp待發(fā)送的數(shù)據(jù),長度是15,數(shù)據(jù)內容是"Hello My Friend",這樣我們就可以按照如下去定義了。其中 zbuffer->data 為定義數(shù)據(jù)的地址,len表示數(shù)據(jù)的長度。
開辟空間之后使用
我們使用的時候, 只需要開辟一次空間即可。
#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;
其他方法實現(xiàn)一些不定長數(shù)據(jù)的傳輸
除了0數(shù)組之外,還有使用定長數(shù)組和指針數(shù)組實現(xiàn)柔性數(shù)組的功能。
定長數(shù)組
定長數(shù)組顧名思義,就是在結構體里面有個定長的數(shù)組,這個數(shù)組大小是按照我們定義數(shù)據(jù)最大來進行設置的,為了就是防止數(shù)據(jù)儲存的時候溢出。
定義
// 定長緩沖區(qū)
#define MAX_LENGTH 512
struct max_buffer
{
int len;
char data[MAX_LENGTH];
};
不過使用過程中,比如我要發(fā)送 512 字節(jié)的數(shù)據(jù), 如果用定長包, 假設定長包的最大長度 MAX_LENGTH 為 1024, 那么就會浪費 512 個字節(jié)的空間, 也會造成不必要的流量浪費。如果數(shù)組結構對齊放置(這塊知識詳細可以看我之前的數(shù)據(jù)對齊的文章) sizeof(struct max_buffer) = sizeof(int)+ sizieof(char) * MAX_LENGTH
數(shù)據(jù)包的構造
一般來說, 我們會返回一個指向緩沖區(qū)數(shù)據(jù)結構 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 個字節(jié) p->len, 作為包頭(就是多出來的那部分),這個包頭是用來描述緊接著包頭后面的數(shù)據(jù)部分的長度,這里是 1024, 所以前四個字節(jié)賦值為 1024 (既然我們要構造不定長數(shù)據(jù)包,那么這個包到底有多長呢,因此,我們就必須通過一個變量來表明這個數(shù)據(jù)包的長度,這就是len的作用),
而緊接其后的內存是真正的數(shù)據(jù)部分, 通過 p->data, 最后, 進行一個 memcpy() 內存拷貝, 把要發(fā)送的數(shù)據(jù)填入到這段內存當中
釋放空間
當使用完畢釋放數(shù)據(jù)的空間的時候, 直接釋放就可以了
free(mbuffer);
mbuffer = NULL;
使用定長數(shù)組, 作為數(shù)據(jù)緩沖區(qū), 為了避免造成緩沖區(qū)溢出, 數(shù)組的大小一般設為足夠的空間 MAX_LENGTH, 而實際使用過程中, 達到 MAX_LENGTH 長度的數(shù)據(jù)很少, 那么多數(shù)情況下, 緩沖區(qū)的大部分空間都是浪費掉的.
但是使用過程很簡單, 數(shù)據(jù)空間的開辟和釋放簡單, 無需程序員考慮額外的操作
指針數(shù)組
它和0數(shù)組的區(qū)別在于,零數(shù)組最后一個結構體元素定義一個data[0],而指針數(shù)組就是結構體中需要定義一個指針數(shù)組,這里面的指針數(shù)組不需要特定在結構體的最后一個元素。
struct point_buffer
{
char *data;
int len;
};
考慮數(shù)組結構對齊(這塊知識詳細可以看我之前的[數(shù)據(jù)對齊](https://mp.weixin.qq.com/s/35jJQy166-GgR9RaHafhog)的文章), 那么數(shù)據(jù)結構的大小 sizeof(point_buffer)= sizeof(int) + (補齊int與char * 類型的長度值)+ sizeof(char * ),在我的64位編譯環(huán)境中int類型是4byte,char * 類型為8byte,所以補齊的長度為8-4,最終sizeof(point_buffer) 為16byte。
如果結構體加上 _attribute((packed)) 數(shù)據(jù)對齊修飾,則 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);
}
}
分配內存時,需采用兩步
首先, 需為結構體分配一塊內存空間;
其次,再為結構體中的成員變量分配內存空間.
這樣兩次分配的內存是不連續(xù)的, 需要分別對其進行管理. 當使用長度為的數(shù)組時, 則是采用一次分配的原則, 一次性將所需的內存全部分配給它.
釋放
相反, 釋放時也是一樣的.
free(pbuffer->data);
free(pbuffer);
pbuffer = NULL;
使用指針結果作為緩沖區(qū), 只多使用了一個指針大小的空間, 無需使用固定長度的數(shù)組, 不會造成空間的大量浪費.
但那是開辟空間時, 需要額外開辟數(shù)據(jù)域的空間, 施放時候也需要顯示釋放數(shù)據(jù)域的空間, 但是實際使用過程中, 往往在函數(shù)中開辟空間, 然后返回給使用者指向 struct point_buffer 的指針, 這時候我們并不能假定使用者了解我們開辟的細節(jié), 并按照約定的操作釋放空間, 因此使用起來多有不便, 甚至造成內存泄漏
小結:
定長數(shù)組使用方便, 但是卻浪費空間, 指針形式只多使用了一個指針的空間, 不會造成大量空間分浪費, 但是使用起來需要多次分配, 多次釋放。所以最優(yōu)解
0數(shù)組的優(yōu)劣以及注意事項
優(yōu)點 :比起在結構體中聲明一個指針變量、再進行動態(tài)分配的辦法,這種方法效率要高。因為在訪問數(shù)組內容時,不需要間接訪問,避免了兩次訪存。此外,0數(shù)組也不會像定長數(shù)組會造成一定的內存的浪費。
缺點 :在結構體中,數(shù)組為0的數(shù)組必須在最后聲明,使用上有一定限制。
結語
這就是我分享的零數(shù)組,如果大家有更好的想法和需求,也歡迎大家加我好友交流分享哈。
—END—
推薦閱讀
【3】CPU中的程序是怎么運行起來的 必讀
【4】cartographer環(huán)境建立以及建圖測試
本公眾號全部原創(chuàng)干貨已整理成一個目錄,回復[ 資源 ]即可獲得。
參考文章:https://blog.csdn.net/gatieme/article/details/64131322
