本文將flexible array member翻譯爲彈性數組(成員),將介紹彈性數組的語法,好處與代價,以及擴展聊聊關於c語言操作內存靈活性方面的思考。

語法

struct Foo {
    int a;
    char b[]; // 有時也寫成 char b[0];
};

如上面例子展示,將結構體的最後一個數據成員定義爲不寫長度(或長度爲0)的數組即爲彈性數組。

這種寫法,b數據成員不佔用大小,看如下代碼:

struct Foo foo;
printf("%d %d\n", sizeof(foo), sizeof(foo.a));

在我的mac電腦上使用 clang-1100.0.33.12 編譯,打印結果爲 4 4 ,說明foo變量大小等於foo變量中a數據成員的大小。

使用如下方法爲彈性數組分配內存會產生編譯錯誤:

foo.b = malloc(128);

編譯錯誤信息: error: array type 'char []' is not assignable

正確的使用方式是:

struct Foo *foo = malloc(sizeof(struct Foo) + 128);

該方式共申請了 4+128 字節大小的內存,該132字節內存是連續的,前4個字節分配給 foo->a ,後128字節可以通過 foo->b 訪問。

好處與代價

一般來說,彈性數組用於元素個數在運行期動態決定的場景。你可能會說,爲什麼不直接用指針呢,就像下面這樣:

struct Foo {
    int a;
    char *b;
};

上面這種寫法確實可以實現同樣的功能,但是存儲相同大小的數據時,兩種方式存在一些區別:

第一,使用這種寫法,b指針變量本身要佔用內存,注意,不管你是否爲b指針分配內存,即使 b==NULL ,變量自身都需要佔用內存。在64位系統,一個指針變量是8個字節,別小看這8個字節,在內存總量比較小的場景,或結構體變量非常多的場景還是很客觀的。

第二,使用彈性數組,彈性數組的內存地址和它之前的數據成員的地址是連續的。訪問時內存的空間局部性也更好些。

但是話說回來,使用彈性數組並不只有好處,它也有代價。

彈性數組的方式,由於結構體中的數據成員和後面掛着的這個數據成員是通過一個malloc申請的內存,這也意味:

第一,整個結構體都要分配在堆上。

第二,當需要對數組內存進行擴容時,你需要對整塊內存realloc。

彈性數組是語法糖,有威力的是c語言操作內存的自由度

其實,我們不使用彈性數組也可以達到彈性數組的效果,如下面代碼:

struct Foo {
    int a;
};

struct Foo *foo = malloc(sizeof(struct Foo) + 128);
char *b = (char *)foo + sizeof(struct Foo);

彈性數組只是一個語法糖,它在結構體最後增加一個成員變量,讓我們可以使用 foo->b 這種方式,直接訪問結構體之後的內存。事實上,你如果自己計算偏移量,也可以到達一樣的效果。

這裏要撇開彈性數組,聊聊閒篇。

在操作內存方面,c語言給它的使用者提供了非常高的自由度,它自身並不標記內存中存儲的是什麼類型的數據,使用者可以對內存地址做任意前後偏移,通過指針類型強轉,解引用,可以把內存按任意類型解析,寫入,讀取。當然,前提是不要發生越界,並且讀寫一致,邏輯符合使用者的預期。

自由度越高,就越可以在更多的場景做更多的優化。但帶來的代價,則是和高級語言相比,可讀性差些,也容易寫出bug。當然,這句話只對於同等水平的初中級程序員有效哈,高手可以無視。

語法補充

最後,對語法做些補充。

第一,在我的環境,如果使用 char b[] 這種寫法,再使用 sizeof(foo.b) 獲取b數據成員大小,將產生編譯錯誤:

error: invalid application of 'sizeof' to an incomplete type 'char []'

第二,彈性數組一般作爲結構體的最後一個數據成員出現,如下代碼會產生編譯錯誤:

struct Foo {
    char b[];
    int a;
};

編譯錯誤信息: error: flexible array member 'b' with type 'char []' is not at the end of struct

原文鏈接: https://pengrl.com/p/20013/

原文出處: yoko blog ( https://pengrl.com )

原文作者: yoko ( https://github.com/q191201771 )

版權聲明:本文歡迎任何形式轉載,轉載時完整保留本聲明信息(包含原文鏈接、原文出處、原文作者、版權聲明)即可。本文後續所有修改都會第一時間在原始地址更新。

相關文章