柔性陣列(Redis原始碼學習)
1. 問題背景
在閱讀Redis原始碼中的字串有如下結構,在sizeof(struct sdshdr)得到結果為8,在后續記憶體申請和計算中也用到,其實在作業中有遇到過這種 struct結構 + 應用資料的情況,但沒有意識到自己使用的是柔性陣列,在學習閱讀Redis代碼中,遇到該方法,就特總結記錄之,
/* * 型別別名,用于指向 sdshdr 的 buf 屬性 */
typedef char * sds;
/* * 保存字串物件的結構 */
struct sdshdr {
// buf 中已占用空間的長度
int len;
// buf 中剩余可用空間的長度
int free;
// 資料空間
char buf[];
};
2. 柔性陣列
柔性陣列(flexible array member)也叫伸縮性陣列成員,這種結構產生與對動態結構體的去求,在日常編程中,有時需要在結構體中存放一個長度是動態的字串(也可能是其他資料型別),
一般的做法,是在結構體中定義一個指標成員,這個指標成員指向該字串所在的動態記憶體空間,在通常情況下,如果想要高效的利用記憶體,那么在結構體內部定義靜態的陣列是非常浪費的行為,其實柔性陣列的想法和動態陣列的想法是一樣的,
柔性陣列用來在結構體中存放一個長度動態的字串,
本文基于redis 的sds.c原始碼,進行簡單編碼驗證測驗,其實這種柔性陣列,在作業中用到過,但是沒有意識到這是柔性陣列,
上述struct sdshdr結構中,要注意:最后一個變數 buf 陣列中,沒有長度,這和自己遇到的正常的使用方式不一樣,新的知識點
這種用法是C語言中的柔性陣列,上面 的sizeof(sdshdr )結果是8,即后面的buf不占空間,只是一個符號,測驗上面sdshdr結果如下:
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上執行上面代碼塊得到結果如下:
$ ./sdshdr
int len:4
sdshdr len:8
Address:
t 0x7fff9572fa50
t.len 0x7fff9572fa50
t.free 0x7fff9572fa54
t.buf 0x7fff9572fa58
可以看到 t.buf 是該結構的最后的地址,是最后一個點,簡單圖示如下:

如果后續再malloc相關的記憶體,則就會在t.buf后面連續,簡單撰寫代碼進行驗證,要加入對應的sds.h檔案,或者直接將結構定義在main函式之前,
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;
}
上述代碼進行編譯,獲得可執行檔案,執行結果如下:
$ ./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開始便支持了不完整型別實作柔性陣列成員,為什么使用不完整型別呢?
```C language
int a[] = {10};
看到這個宣告陳述句,我們發現a[]其實就是個陣列記號,不完整型別,由于賦值陳述句,所以在編譯時便確定了陣列的大小,是一個完整的陣列型別,
在結構體中便利用不完整型別在運行對動態的陣列進行指明,
C99標準的定義如下:
struct Test{
int a;
char p[]; // 不只是char型別,其他型別同樣也是可以
}
由于宣告記憶體連續性的關系,柔性陣列成員必須定義在結構體的最后一個,并且不能是唯一的成員,
我們再來看一看整個結構體(包含陣列記憶體的分布情況),進行簡單編碼驗證,
#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); //只需要釋放一次記憶體
return 0;
}
在linux上的執行結果如下:
$ ./sdshdr
sizeof(int):4,sizeof(Test):4
t->p:hello
Address:
t 0x7e0010
t.a 0x7e0010
t.p 0x7e0014
4. 小結
- 在結構體中存放一個長度是動態資料型別時,可以考慮到柔性陣列,
- 一般做法,是在結構體中定義一個指標成員,這個指標成員指向所在的動態記憶體空間,
- 該指標成員,不占結構體空間,只是一個符號,
- 柔性陣列成員必須定義在結構體的最后一個,并且不能是唯一的成員,
5. 參考文獻
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
本人才疏學淺,參考網路文章及代碼驗證,如有錯誤不當之處,請批評指正,如有侵權,請立即聯系我進行洗掉,
如果能為您帶來一點點幫助,那將是我的榮幸,多謝您關注和轉發推薦,謝謝!

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/288810.html
標籤:NoSQL
