柔性數組的概念
柔性數組(flexible array member)也叫伸縮性數組成員,這種結構產生與對動態結構體的去求。在日常編程中,有時需要在結構體中存放一個長度是動態的字符串(也可能是其他數據類型),一般的做法,實在結構體中定義一個指針成員,這個指針成員指向該字符串所在的動態內存空間。
在通常情況下,如果想要高效的利用內存,那么在結構體內部定義靜態的數組是非常浪費的行為。其實柔性數組的想法和動態數組的想法是一樣的。
柔性數組用來在結構體中存放一個長度動態的字符串。
普通的方式
其實不用柔性數組我們一樣可以做到:在結構體中定義一個方法,在方法中動態地將指針指向動態數組
#include <string.h> #include <stdlib.h> typedef struct Test { int a; char *p; } Test; int main() { char *str = (char *)malloc(sizeof(char) * 10); strcpy(str, "hello"); Test *t = (Test *)malloc(sizeof(Test)); t->p = str; printf("%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); free(t->p); //還需要顯式的釋放p所指的內存 return 0; }
運行結果:
上面的代碼的確是可以完成我們想要的結果。我們看了一下指針p和數組的起始地址。我們可以看到動態數組的內存塊和字符串的內存是兩塊不一樣的內存。
但是,我們在釋放空間時,需要顯式地釋放指針p引用的內存空間,不然會出現內存泄露的情況。
使用柔性數組的方式
從C99開始便支持了不完整類型實現柔性數組成員。為什么使用不完整類型呢?
int a[] = {10};
看到這個聲明語句,我們發現a[]其實就是個數組記號,不完整類型,由於賦值語句,所以在編譯時便確定了數組的大小,是一個完整的數組類型。
在結構體中便利用不完整類型在運行對動態的數組進行指明。
C99標准的定義如下
struct Test{ int a; char p[]; // 不只是char類型,其他類型同樣也是可以 }
由於聲明內存連續性的關系,柔性數組成員必須定義在結構體的最后一個,並且不能是唯一的成員。
我們再來看一看整個結構體(包含數組內存的分布情況)
#include <string.h> #include <stdlib.h> typedef struct Test { int a; char p[]; } Test; int main() { Test *t=(Test*)malloc(sizeof(Test)+sizeof(char)*(10+1)); strcpy(t->p,"hello"); printf("%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; }
再運行結果:
由運行結果就可以看出,整個結構體是連續的,並且釋放結構體的方式也非常簡單直接對結構體指針進行釋放。
【注】由於這個是C99的標准,在ISO C和C++的規格說明書中是不允許的。在vs下使用0長度的數組可能會得到一個警告。
然而gcc, clang++預先支持了C99的玩法,所以在Linux下編譯無警告
進一步認識柔性數組
現有一個程序
#include<stdio.h> typedef struct _SoftArray{ int len; int array[]; }SoftArray; int main() { int len = 10; printf("The struct's size is %d\n",sizeof(SoftArray)); }
運行結果:
我們可以看出,_SoftArray結構體的大小是4,顯然,在32位操作系統下一個int型變量大小剛好為4,也就說結構體中的數組沒有占用內存。為什么會沒有占用內存,我們平時用數組時不時都要明確指明數組大小的嗎?但這里卻可以編譯通過呢?這就是我們常說的動態數組,也就是柔性數組。
先不要亂,讓我們再看一段代碼
#include <stdio.h> #include <malloc.h> typedef struct _SoftArray { int len; int array[]; } SoftArray; int main() { int len = 10; SoftArray *p = (SoftArray *)malloc(sizeof(SoftArray) + sizeof(int) * len); printf("After the malloc function the struct's size is %d\n",sizeof(SoftArray)); return 0; }
運行結果:
是不是有點奇怪,為什么申請了內存后結構體大小還是4呢?
原因是:動態申請的內存只是申請給數組拓展所用,從上個程序我們可以看出結構體的大小在創建時已經確定了,array明確來說不算是結構體成員,只是掛羊頭賣狗肉而已。
下面我們來看看關於柔性數組的資料:
1、什么是柔性數組?
柔性數組既數組大小待定的數組, C語言中結構體的最后一個元素可以是大小未知的數組,也就是所謂的0長度,所以我們可以用結構體來創建柔性數組。
2、柔性數組有什么用途 ?
它的主要用途是為了滿足需要變長度的結構體,為了解決使用數組時內存的冗余和數組的越界問題。
3、用法:在一個結構體的最后 ,申明一個長度為空的數組,就可以使得這個結構體是可變長的。對於編譯器來說,此時長度為0的數組並不占用空間,因為數組名
本身不占空間,它只是一個偏移量, 數組名這個符號本身代 表了一個不可修改的地址常量 (注意:數組名永遠都不會是指針! ),但對於這個數組的大小,我們可以進行動態分配,對於編譯器而言,數組名僅僅是一個符號,它不會占用任何空間,它在結構體中,只是代表了一個偏移量,代表一個不可修改的地址常量!
對於柔性數組的這個特點,很容易構造出變成結構體,如緩沖區,數據包等等:
typedef struct _SoftArray { Int len; int array[]; }SoftArray;
柔性數組用途
這樣的變長數組常用於網絡通信中構造不定長數據包,不會浪費空間浪費網絡流量,比如我要發送1024字節的數據,如果用定長包,假設定長包的長度為2048,就會浪費1024個字節的空間,也會造成不必要的流量浪費。
其實柔性數組成員在實現跳躍表時有它特別的用法,在Redis的SDS數據結構中和跳躍表的實現上,也使用柔性數組成員。