哦!數(shù)組還能這么用,學到了!
這個問題源于讀者在閱讀redis源碼時的一個疑問。
先看下面的代碼,對于包含動態(tài)字符串成員的兩個結(jié)構(gòu)體Test0和Test1占用空間分別是多少呢?
- //來源:公眾號【編程珠璣】
- //作者:守望先生
- #include<stdio.h>
- struct Test0
- {
- int a;
- int b;
- char *c;
- };
- struct Test1
- {
- int a;
- int b;
- char c[];
- };
- int main(void)
- {
- printf("sizeof(struct Test0) = %zd\n",sizeof(struct Test0));
- printf("sizeof(struct Test1) = %zd\n",sizeof(struct Test1));
- return 0;
- }
很多讀者一眼就能看出來,在64位系統(tǒng)上,編譯為64位程序,其輸出結(jié)果為:
- 16
- 8
對于Test0的結(jié)果是16,通常沒有什么疑問,畢竟4(int)+4(int)+8(指針)= 16,但是對于后者的結(jié)構(gòu)體占用空間為8字節(jié),有的讀者可能會有疑問。(關于字節(jié)對齊,參考《字節(jié)對齊,看這篇就懂了》)
柔性數(shù)組(flexible array)
實際上這是在C99中引入的柔性數(shù)組的特性。即結(jié)構(gòu)體的最后一個成員,可以不完整類型(一種缺乏足夠的信息去描述一個完整對象的類型)的數(shù)組,但它使得整個結(jié)構(gòu)體的大小就像沒有這個成員一樣。但是呢,當用結(jié)構(gòu)體通過這個名字訪問這個成員時,就像訪問一個普通數(shù)組成員一樣。
如果數(shù)組最終一個元素都沒有的話,那么訪問這個數(shù)組將會是未定義行為了。
正如我們前面所看到的:
- struct Test1
- {
- int a;
- int b;
- char c[];
- };
成員c是一個數(shù)組,但是并沒有指定大小,使用sizeof計算Test1,其占用空間也僅僅是8字節(jié)。
有什么好處?
那么使用柔性數(shù)組有什么好處呢?
內(nèi)存申請和釋放
假設分別使用兩種類型的結(jié)構(gòu)體,存儲16字節(jié)的字符數(shù)據(jù),需要申請內(nèi)存。對于struct Test0:
- strcut Test0 *t0 = malloc(sizeof(struct Test0));//為結(jié)構(gòu)體申請內(nèi)存
- t0->c = malloc(sizeof(char) * 16);//為成員指向的數(shù)據(jù)申請內(nèi)存
而對于struct Test1:
- strcut Test1 *t1 = malloc(sizeof(struct Test1) + sizeof(char) * 16);
看出區(qū)別了嗎?前者需要兩次內(nèi)存申請,而后者只需要一次。前者地址不連續(xù)(兩次malloc),后者地址連續(xù)。而你訪問成員c的時候,只需要下面這樣就可以:
t1->c,和普通成員無異。
要判斷它們的地址是否連續(xù)也非常簡單,只需要分別打印b和c的地址就可以了。
和內(nèi)存釋放類似,前面需要單獨釋放成員c申請的內(nèi)存,而后者可以一起釋放。
數(shù)據(jù)拷貝
正由于前面的差別,導致數(shù)據(jù)拷貝時,更有區(qū)別。
對于struct Test0:
- //memcpy(t0copy,t0,sizeof(struct Test0));//不可,這樣直接t0copy的c和t0的c指向同一片內(nèi)存區(qū)域。
- t0t0copy.a = t0.a;
- t0t0copy.b = t0.b;
- memcpy(t0copy.c,t0.c,sizeof(char)*16);
這里無法一次拷貝,因為它的成員c是一個指針類型,我們需要的是一份完整拷貝,因此必須拷貝它指向的內(nèi)存。(參考《結(jié)構(gòu)體成員賦值到底是深拷貝還是淺拷貝?》)
但是對于struct Test1:
- memcpy(t0copy,t0,sizeof(strcut Test1) + sizeof(char) * 16);
在這里,由于柔性數(shù)組的內(nèi)存,它的數(shù)據(jù)內(nèi)容和結(jié)構(gòu)體數(shù)據(jù)成員的地址是連續(xù)的,因此可以直接拷貝。
減少內(nèi)存碎片
由于結(jié)構(gòu)體的柔性數(shù)組和結(jié)構(gòu)體成員的地址是連續(xù)的,即可一同申請內(nèi)存,因此更大程度地避免了內(nèi)存碎片。另外由于該成員本身不占結(jié)構(gòu)體空間,因此,整體而言,比普通的數(shù)組成員占用空間要會稍微小點。
零長數(shù)組
與柔性數(shù)組功能類似,還有一個0長數(shù)組,不過它并不是標準中的,但是它可以實現(xiàn)類似的功能,使用方式如下:
- struct Test1
- {
- int a;
- int b;
- char c[0];
- };
差別在于使得數(shù)組長度為0。但是由于它并非C標準中的,因此從可移植性考慮,不建議使用這種方式,除非你還無法使用C99。
總結(jié)
柔性數(shù)組的使用:
- 位于結(jié)構(gòu)體最后一個位置
- 不完整數(shù)組類型
- 不是唯一成員
最后,放張圖,看差別:
普通和柔性數(shù)組