天天看點

聽說有人不了解柔性數組

1 引言

定長數組包

在平時的開發中,緩沖區資料收發時,如果采用緩沖區定長包,假定大小是 1k,MAX_LENGTH 為 1024。結構體如下:

//  定長緩沖區
struct max_buffer
{
    int   len;
    char  data[MAX_LENGTH];
};      

資料結構的大小 >= sizeof(int) + sizeof(char) * MAX_LENGTH為了防止資料溢出的情況,data 的長度一般會設定得足夠大,但也正是因為這樣,才會導緻數組的備援。

假如發送 512 位元組的資料,  就會浪費 512 個位元組的空間, 平時通信時,大多數是心跳包,大小遠遠小于 1024,除了浪費空間還消耗很多流量。

記憶體申請:

if ((m_buffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
{
    m_buffer->len = CUR_LENGTH;
    memcpy(m_buffer->data, "max_buffer test", CUR_LENGTH);
    printf("%d, %s\n", m_buffer->len, m_buffer->data);
}      

記憶體釋放:

free(m_buffer);

m_buffer = NULL;

指針資料包

為了避免空間上的浪費,我們可以将上面的長度為 MAX_LENGTH 的定長數組換為指針, 每次使用時動态的開辟 CUR_LENGTH 大小的空間。資料包結構體定義:

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

資料結構大小 >= sizeof(int) + sizeof(char *)但在記憶體配置設定時,需要兩步進行:

需為結構體配置設定一塊記憶體空間;

為結構體中的成員變量配置設定記憶體空間;

if ((p_buffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
{
    p_buffer->len = CUR_LENGTH;
    if ((p_buffer->data = (char *)malloc(sizeof(char) * CUR_LENGTH)) != NULL)
    {
        memcpy(p_buffer->data, "point_buffer test", CUR_LENGTH);
        printf("%d, %s\n", p_buffer->len, p_buffer->data);
    }
}      
free(p_buffer->data);
free(p_buffer);
p_buffer = NULL;      

雖然這樣能夠節約記憶體,但是兩次配置設定的記憶體是不連續的, 需要分别對其進行管理,導緻的問題就是需要對結構體和資料分别申請和釋放記憶體,這樣對于程式員來說無疑是一個災難,因為這樣很容易導緻遺忘釋放記憶體造成記憶體洩露。

有沒有更好的方法呢?那就是今天的主題柔性數組。

2 柔性數組

什麼是柔性數組?

柔性數組成員(flexible array member)也叫伸縮性數組成員,這種代碼結構産生于對動态結構體的需求。在日常的程式設計中,有時候需要在結構體中存放一個長度動态的字元串,鑒于這種代碼結構所産生的重要作用,C99 甚至把它收入了标準中:

As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member.

柔性數組是 C99 标準引入的特性,是以當你的編譯器提示不支援的文法時,請檢查你是否開啟了 C99 選項或更高的版本支援。

C99 标準的定義如下:

struct test {
    short len;  // 必須至少有一個其它成員
    char arr[]; // 柔性數組必須是結構體最後一個成員(也可是其它類型,如:int、double、...)
};      

柔性數組成員必須定義在結構體裡面且為最後元素;

結構體中不能單獨隻有柔性數組成員;

柔性數組不占記憶體。

在一個結構體的最後,申明一個長度為空的數組,就可以使得這個結構體是可變長的。對于編譯器來說,此時長度為 0 的數組并不占用空間,因為數組名本身不占空間,它隻是一個偏移量,數組名這個符号本身代表了一個不可修改的位址常量,

但對于這個數組的大小,我們可以進行動态配置設定,對于編譯器而言,數組名僅僅是一個符号,它不會占用任何空間,它在結構體中,隻是代表了一個偏移量,代表一個不可修改的位址常量!

對于柔性數組的這個特點,很容易構造出變成結構體,如緩沖區,資料包等等, 其實柔性數組成員在實作跳躍表時有它特别的用法,在Redis的SDS資料結構中和跳躍表的實作上,也使用柔性數組成員。它的主要用途是為了滿足需要變長度的結構體,為了解決使用數組時記憶體的備援和數組的越界問題。

柔性數組解決引言的例子

//柔性數組
struct soft_buffer
{
    int   len;
    char  data[0];
};      

資料結構大小 = sizeof(struct soft_buffer) = sizeof(int),這樣的變長數組常用于網絡通信中構造不定長資料包, 不會浪費空間浪費網絡流量。

申請記憶體:

if ((softbuffer = (struct soft_buffer *)malloc(sizeof(struct soft_buffer) + sizeof(char) * CUR_LENGTH)) != NULL)
{
    softbuffer->len = CUR_LENGTH;
    memcpy(softbuffer->data, "softbuffer test", CUR_LENGTH);
    printf("%d, %s\n", softbuffer->len, softbuffer->data);
}      

釋放記憶體:

free(softbuffer);

softbuffer = NULL;

對比使用指針和柔性數組會發現,使用柔性數組的優點:

由于結構體使用指針位址不連續(兩次 malloc),柔性數組位址連續,隻需要一次 malloc,同樣釋放前者需要兩次,後者可以一起釋放。

在資料拷貝時,結構體使用指針時,必須拷貝它指向的記憶體,記憶體不連續會存在問題,柔性數組可以直接拷貝。

減少記憶體碎片,由于結構體的柔性數組和結構體成員的位址是連續的,即可一同申請記憶體,是以更大程度地避免了記憶體碎片。另外由于該成員本身不占結構體空間,是以,整體而言,比普通的數組成員占用空間要會稍微小點。

缺點:對結構體格式有要求,必要放在最後,不是唯一成員。

3 總結

在日常程式設計中,有時需要在結構體中存放一個長度是動态的字元串(也可能是其他資料類型),可以使用柔性數組,柔性數組是一種能夠巧妙地解決數組記憶體的備援和數組的越界問題一種方法。非常值得大家學習和借鑒。

繼續閱讀