柔性數(shù)組(Redis源碼學(xué)習(xí))
1. 問(wèn)題背景
在閱讀Redis源碼中的字符串有如下結(jié)構(gòu),在sizeof(struct sdshdr)
得到結(jié)果為8岸裙,在后續(xù)內(nèi)存申請(qǐng)和計(jì)算中也用到鹏溯。其實(shí)在工作中有遇到過(guò)這種 struct結(jié)構(gòu) + 應(yīng)用數(shù)據(jù)的情況,但沒(méi)有意識(shí)到自己使用的是柔性數(shù)組,在學(xué)習(xí)閱讀Redis代碼中,遇到該方法,就特總結(jié)記錄之在验。
/* * 類型別名,用于指向 sdshdr 的 buf 屬性 */
typedef char * sds;
/* * 保存字符串對(duì)象的結(jié)構(gòu) */
struct sdshdr {
// buf 中已占用空間的長(zhǎng)度
int len;
// buf 中剩余可用空間的長(zhǎng)度
int free;
// 數(shù)據(jù)空間
char buf[];
};
2. 柔性數(shù)組
柔性數(shù)組(flexible array member)也叫伸縮性數(shù)組成員堵未,這種結(jié)構(gòu)產(chǎn)生與對(duì)動(dòng)態(tài)結(jié)構(gòu)體的去求腋舌。在日常編程中,有時(shí)需要在結(jié)構(gòu)體中存放一個(gè)長(zhǎng)度是動(dòng)態(tài)的字符串(也可能是其他數(shù)據(jù)類型)渗蟹。
一般的做法块饺,是在結(jié)構(gòu)體中定義一個(gè)指針成員,這個(gè)指針成員指向該字符串所在的動(dòng)態(tài)內(nèi)存空間雌芽。在通常情況下刨沦,如果想要高效的利用內(nèi)存,那么在結(jié)構(gòu)體內(nèi)部定義靜態(tài)的數(shù)組是非常浪費(fèi)的行為膘怕。其實(shí)柔性數(shù)組的想法和動(dòng)態(tài)數(shù)組的想法是一樣的想诅。
柔性數(shù)組用來(lái)在結(jié)構(gòu)體中存放一個(gè)長(zhǎng)度動(dòng)態(tài)的字符串。
本文基于redis 的sds.c源碼岛心,進(jìn)行簡(jiǎn)單編碼驗(yàn)證測(cè)試来破,其實(shí)這種柔性數(shù)組,在工作中用到過(guò)忘古,但是沒(méi)有意識(shí)到這是柔性數(shù)組徘禁。
上述struct sdshdr
結(jié)構(gòu)中,要注意:最后一個(gè)變量 buf 數(shù)組中髓堪,沒(méi)有長(zhǎng)度送朱,這和自己遇到的正常的使用方式不一樣,新的知識(shí)點(diǎn)
這種用法是C語(yǔ)言中的柔性數(shù)組干旁,上面 的sizeof(sdshdr )結(jié)果是8驶沼,即后面的buf不占空間,只是一個(gè)符號(hào)争群,測(cè)試上面sdshdr結(jié)果如下:
int main(int argc,char **argv){
struct sdshdr t;
printf("int len:%d\n",sizeof(int));
printf("sdshdr len:%d\n",sizeof(struct sdshdr));
printf("Address:\n");
printf("t\t %p\n", &t);
printf("t.len\t %p\n", &(t.len));
printf("t.free\t %p\n", &(t.free));
printf("t.buf\t %p\n", &(t.buf));
return 0;
}
RHEL6.9上執(zhí)行上面代碼塊得到結(jié)果如下:
$ ./sdshdr
int len:4
sdshdr len:8
Address:
t 0x7fff9572fa50
t.len 0x7fff9572fa50
t.free 0x7fff9572fa54
t.buf 0x7fff9572fa58
可以看到 t.buf 是該結(jié)構(gòu)的最后的地址回怜,是最后一個(gè)點(diǎn),簡(jiǎn)單圖示如下:
如果后續(xù)再malloc相關(guān)的內(nèi)存换薄,則就會(huì)在t.buf后面連續(xù),簡(jiǎn)單編寫代碼進(jìn)行驗(yàn)證玉雾。要加入對(duì)應(yīng)的sds.h文件翔试,或者直接將結(jié)構(gòu)定義在main函數(shù)之前。
int main(int argc,char **argv){
struct sdshdr t;
printf("int len:%d\n",sizeof(int));
printf("sdshdr len:%d\n",sizeof(struct sdshdr));
printf("Address:\n");
printf("t\t %p\n", &t);
printf("t.len\t %p\n", &(t.len));
printf("t.free\t %p\n", &(t.free));
printf("t.buf\t %p\n", &(t.buf));
printf("sizeof(char):\t %d\n", sizeof(char));
struct sdshdr *p=(struct sdshdr*)malloc(sizeof(struct sdshdr) + sizeof(char)*8);
printf("After malloc the struct's size is %d\n",sizeof(struct sdshdr));
printf("Address:\n");
printf("p\t %p\n", p);
printf("p->len\t %p\n", &(p->len));
printf("p->free\t %p\n", &(p->free));
printf("p->buf\t %p,sizeof(p):%d\n", &(p->buf),sizeof(p));
memset(p,0,sizeof(struct sdshdr) + sizeof(char)*8);
char *str="Hello";
memcpy(p->buf,str,strlen(str));
printf("p->buf:%s\n",p->buf);
char *str1="HelloWorldttttttt";
memcpy(p->buf,str1,sizeof(char)*8-1);
printf("p->buf:%s\n",p->buf);
printf("strlen(p->buf):%d\n",strlen(p->buf));
return 0;
}
上述代碼進(jìn)行編譯复旬,獲得可執(zhí)行文件垦缅,執(zhí)行結(jié)果如下:
$ ./sdshdr
int len:4
sdshdr len:8
Address:
t 0x7ffea0a8c420
t.len 0x7ffea0a8c420
t.free 0x7ffea0a8c424
t.buf 0x7ffea0a8c428
sizeof(char): 1
After malloc the struct's size is 8
Address:
p 0x1bc3010
p->len 0x1bc3010
p->free 0x1bc3014
p->buf 0x1bc3018,sizeof(p):8
p->buf:Hello
p->buf:HelloWo
strlen(p->buf):7
$
3. 使用方法
從C99開始便支持了不完整類型實(shí)現(xiàn)柔性數(shù)組成員。為什么使用不完整類型呢驹碍?
int a[] = {10};
看到這個(gè)聲明語(yǔ)句失都,我們發(fā)現(xiàn)a[]其實(shí)就是個(gè)數(shù)組記號(hào),不完整類型幸冻,由于賦值語(yǔ)句,所以在編譯時(shí)便確定了數(shù)組的大小咳焚,是一個(gè)完整的數(shù)組類型洽损。
在結(jié)構(gòu)體中便利用不完整類型在運(yùn)行對(duì)動(dòng)態(tài)的數(shù)組進(jìn)行指明。
C99標(biāo)準(zhǔn)的定義如下:
struct Test{
int a;
char p[]; // 不只是char類型革半,其他類型同樣也是可以
}
由于聲明內(nèi)存連續(xù)性的關(guān)系碑定,柔性數(shù)組成員必須定義在結(jié)構(gòu)體的最后一個(gè),并且不能是唯一的成員又官。
我們?cè)賮?lái)看一看整個(gè)結(jié)構(gòu)體(包含數(shù)組內(nèi)存的分布情況),進(jìn)行簡(jiǎn)單編碼驗(yàn)證延刘。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Test
{
int a;
char p[];
} Test;
int main()
{
Test *t=(Test*)malloc(sizeof(Test)+sizeof(char)*(10+1));
printf("sizeof(int):%d,sizeof(Test):%d\n",sizeof(int),sizeof(Test));
strcpy(t->p,"hello");
printf("t->p:%s\n", (t->p));
printf("Address:\n");
printf("t\t %p\n", t);
printf("t.a\t %p\n", &(t->a));
printf("t.p\t %p\n", (t->p));
free(t); //只需要釋放一次內(nèi)存
return 0;
}
在linux上的執(zhí)行結(jié)果如下:
$ ./sdshdr
sizeof(int):4,sizeof(Test):4
t->p:hello
Address:
t 0x7e0010
t.a 0x7e0010
t.p 0x7e0014
4. 小結(jié)
- 在結(jié)構(gòu)體中存放一個(gè)長(zhǎng)度是動(dòng)態(tài)數(shù)據(jù)類型時(shí),可以考慮到柔性數(shù)組六敬。
- 一般做法碘赖,是在結(jié)構(gòu)體中定義一個(gè)指針成員,這個(gè)指針成員指向所在的動(dòng)態(tài)內(nèi)存空間外构。
- 該指針成員普泡,不占結(jié)構(gòu)體空間,只是一個(gè)符號(hào)审编。
- 柔性數(shù)組成員必須定義在結(jié)構(gòu)體的最后一個(gè)撼班,并且不能是唯一的成員。
5. 參考文獻(xiàn)
https://www.cnblogs.com/davygeek/p/5748852.html
https://blog.csdn.net/qq_40477151/article/details/78905567
https://www.cnblogs.com/pluviophile/p/7571410.html
本人才疏學(xué)淺垒酬,參考網(wǎng)絡(luò)文章及代碼驗(yàn)證砰嘁,如有錯(cuò)誤不當(dāng)之處,請(qǐng)批評(píng)指正勘究。
如果能為您帶來(lái)一wx點(diǎn)hongmaolinux點(diǎn)幫助矮湘,那將是我的榮幸,歡迎您關(guān)注口糕,轉(zhuǎn)發(fā)推薦板祝,謝謝!