其實很早在看LINUX下就看到這個東西,後來在MFC內存池裏一樣也看到了相似的東西,還依照MFC寫過一個相似的小內存池,(MFC用的是return this + 1)後來在李先靜的《系統程序員成長計劃》裏看到了相似的定義,因而內心想着總結一下,結果發現網上已經有牛人總結的很好了,因而乎就轉了過來,謝謝大家的分享,這是我前進的動力!linux
同時,須要引發注意的:ISO/IEC 9899-1999裏面,這麼寫是非法的,這個僅僅是GNU C的擴展,gcc能夠容許這一語法現象的存在。但最新的C/C++不知道是否能夠,我沒有測試過。(C99容許。微軟的VS系列報一個WARNING,即很是的標準擴展。)程序員
結構體最後使用0或1的長度數組的緣由,主要是爲了方便的管理內存緩衝區,若是你直接使用指針而不使用數組,那麼,你在分配內存緩衝區時,就必須分配結構體一次,而後再分配結構體內的指針一次,(而此時分配的內存已經與結構體的內存不連續了,因此要分別管理即申請和釋放)而若是使用數組,那麼只須要一次就能夠所有分配出來,(見下面的例子),反過來,釋放時也是同樣,使用數組,一次釋放,使用指針,得先釋放結構體內的指針,再釋放結構體。還不能顛倒次序。其實就是分配一段連續的的內存,減小內存的碎片化。數組
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 數據結構
在Linux系統裏,/usr/include/linux/if_pppox.h裏面有這樣一個結構: ide
struct pppoe_tag {
__u16 tag_type;
__u16 tag_len;
char tag_data[0];
} __attribute ((packed));
最後一個成員爲可變長的數組,對於TLV(Type-Length-Value)形式的結構,或者其餘須要變長度的結構體,用這種方式定義最好。使用起來很是方便,建立時,malloc一段結構體大小加上可變長數據長度的空間給它,可變長部分可按數組的方式訪問,釋放時,直接把整個結構體free掉就能夠了。例子以下:
struct pppoe_tag *sample_tag;
__u16 sample_tag_len = 10;
sample_tag = (struct pppoe_tag *)malloc(sizeof(struct pppoe_tag)+sizeof(char)*sample_tag_len);
sample_tag->tag_type = 0xffff;
sample_tag->tag_len = sample_tag_len;
sample_tag->tag_data[0]=....
...
釋放時,
free(sample_tag)函數
是否能夠用 char *tag_data 代替呢?其實它和 char *tag_data 是有很大的區別,爲了說明這個問題,我寫了如下的程序:
例1:test_size.c
10 struct tag1
20 {
30 int a;
40 int b;
50 }__attribute ((packed));
60
70 struct tag2
80 {
90 int a;
100 int b;
110 char *c;
120 }__attribute ((packed));
130
140 struct tag3
150 {
160 int a;
170 int b;
180 char c[0];
190 }__attribute ((packed));
200
210 struct tag4
220 {
230 int a;
240 int b;
250 char c[1];
260 }__attribute ((packed));
270
280 int main()
290 {
300 struct tag2 l_tag2;
310 struct tag3 l_tag3;
320 struct tag4 l_tag4;
330
340 memset(&l_tag2,0,sizeof(struct tag2));
350 memset(&l_tag3,0,sizeof(struct tag3));
360 memset(&l_tag4,0,sizeof(struct tag4));
370
380 printf("size of tag1 = %d\n",sizeof(struct tag1));
390 printf("size of tag2 = %d\n",sizeof(struct tag2));
400 printf("size of tag3 = %d\n",sizeof(struct tag3));
410
420 printf("l_tag2 = %p,&l_tag2.c = %p,l_tag2.c = %p\n",&l_tag2,&l_tag2.c,l_tag2.c);
430 printf("l_tag3 = %p,l_tag3.c = %p\n",&l_tag3,l_tag3.c);
440 printf("l_tag4 = %p,l_tag4.c = %p\n",&l_tag4,l_tag4.c);
450 exit(0);
460 }學習
__attribute ((packed)) 是爲了強制不進行4字節對齊,這樣比較容易說明問題。
程序的運行結果以下:
size of tag1 = 8
size of tag2 = 12
size of tag3 = 8
size of tag4 = 9
l_tag2 = 0xbffffad0,&l_tag2.c = 0xbffffad8,l_tag2.c = (nil)
l_tag3 = 0xbffffac8,l_tag3.c = 0xbffffad0
l_tag4 = 0xbffffabc,l_tag4.c = 0xbffffac4測試
從上面程序和運行結果能夠看出:tag1自己包括兩個32位整數,因此佔了8個字節的空間。tag2包括了兩個32位的整數,外加一個char *的指針,因此佔了12個字節。tag3纔是真正看出char c[0]和char *c的區別,char c[0]中的c並非指針,是一個偏移量,這個偏移量指向的是a、b後面緊接着的空間,因此它其實並不佔用任何空間。tag4更加補充說明了這一點。因此,上面的struct pppoe_tag的最後一個成員若是用char *tag_data定義,除了會佔用多4個字節的指針變量外,用起來會比較不方便:flex
方法一,建立時,能夠首先爲struct pppoe_tag分配一塊內存,再爲tag_data分配內存,這樣在釋放時,要首先釋放tag_data佔用的內存,再釋放pppoe_tag佔用的內存;this
方法二,建立時,直接爲struct pppoe_tag分配一塊struct pppoe_tag大小加上tag_data的內存,從例一的420行能夠看出,tag_data的內容要進行初始化,要讓tag_data指向strct pppoe_tag後面的內存。
struct pppoe_tag {
__u16 tag_type;
__u16 tag_len;
char *tag_data;
} __attribute ((packed));
struct pppoe_tag *sample_tag;
__u16 sample_tag_len = 10;
方法一:
sample_tag = (struct pppoe_tag *)malloc(sizeof(struct pppoe_tag));
sample_tag->tag_len = sample_tag_len;
sample_tag->tag_data = malloc(sizeof(char)*sample_tag_len);
sample_tag->tag_data[0]=...
釋放時:
free(sample_tag->tag_data);
free(sample_tag);
方法二:
sample_tag = (struct pppoe_tag *)malloc(sizeof(struct pppoe_tag)+sizeof(char)*sample_tag_len);
sample_tag->tag_len = sample_tag_len;
sample_tag->tag_data = ((char *)sample_tag)+sizeof(struct pppoe_tag);
sample_tag->tag_data[0]=...
釋放時:
free(sample_tag);
因此不管使用那種方法,都沒有char tag_data[0]這樣的定義來得方便。
講了這麼多,其實本質上涉及到的是一個C語言裏面的數組和指針的區別問題(也就是咱們提到的內存管理問題,數組分配的是在結構體空間地址後一段連續的空間,而指針是在一個隨機的空間分配的一段連續空間)。char a[1]裏面的a和char *b的b相同嗎?《Programming Abstractions in C》(Roberts, E. S.,機械工業出版社,2004.6)82頁裏面說:「arr is defined to be identical to &arr[0]」。也就是說,char a[1]裏面的a實際是一個常量,等於&a[0]。而char *b是有一個實實在在的指針變量b存在。因此,a=b是不容許的,而b=a是容許的。兩種變量都支持下標式的訪問,那麼對於a[0]和b[0]本質上是否有區別?咱們能夠經過一個例子來講明。
例二:
10 #include <stdio.h>
20 #include <stdlib.h>
30
40 int main()
50 {
60 char a[10];
70 char *b;
80
90 a[2]=0xfe;
100 b[2]=0xfe;
110 exit(0);
120 }
編譯後,用objdump能夠看到它的彙編:
080483f0 <main>:
80483f0: 55 push %ebp
80483f1: 89 e5 mov %esp,%ebp
80483f3: 83 ec 18 sub $0x18,%esp
80483f6: c6 45 f6 fe movb $0xfe,0xfffffff6(%ebp)
80483fa: 8b 45 f0 mov 0xfffffff0(%ebp),%eax
80483fd: 83 c0 02 add $0x2,%eax
8048400: c6 00 fe movb $0xfe,(%eax)
8048403: 83 c4 f4 add $0xfffffff4,%esp
8048406: 6a 00 push $0x0
8048408: e8 f3 fe ff ff call 8048300 <_init+0x68>
804840d: 83 c4 10 add $0x10,%esp
8048410: c9 leave
8048411: c3 ret
8048412: 8d b4 26 00 00 00 00 lea 0x0(%esi,1),%esi
8048419: 8d bc 27 00 00 00 00 lea 0x0(%edi,1),%edi
能夠看出,a[2]=0xfe是直接尋址,直接將0xfe寫入&a[0]+2的地址,而b[2]=0xfe是間接尋址,先將b的內容(地址)拿出來,加2,再0xfe寫入計算出來的地址。因此a[0]和b[0]本質上是不一樣的。
但當數組做爲參數時,和指針就沒有區別了。
int do1(char a[],int len);
int do2(char *a,int len);
這兩個函數中的a並沒有任何區別。都是實實在在存在的指針變量。
順便再說一下,對於struct pppoe_tag的最後一個成員的定義是char tag_data[0],某些編譯器不支持長度爲0的數組的定義,在這種狀況下,只能將它定義成char tag_data[1],使用方法相同。
在openoffice的源代碼中看到以下數據結構,是一個unicode字符串結構,他的最後就用長度爲1數組,多是爲了兼容或者跨編譯器。
typedef struct _rtl_uString
{
sal_Int32 refCount;
sal_Int32 length;
sal_Unicode buffer[1];
} rtl_uString;
這是不定長字符串。大概意思是這樣:
rtl_uString * str = malloc(256);
str->length = 256;
str->buffer如今就指向一個長度爲256 - 8的緩衝區
總結:經過上面的轉載的文章,能夠清晰的發現,這種方法的優點其實就是爲了簡化內存的管理,咱們假設在理想的內存狀態下,那麼分配的內存空間,能夠是按序下來的(固然,實際由於內存碎片等的緣由會不一樣的)咱們能夠利用最後一個數組的指針直接無間隔的跳到分配的數組緩衝區,這在LINUX下很是常見,在WINDOWS下的我只是在MFC裏見過相似的,別的狀況下記不清楚了,只記得MFC裏的是這麼講的,能夠用分配的結構體的指針(this)直接+1(詳細的方法請看個人博客:CE分類裏的:內存池技術的應用和詳細說明),就跳到實際的內存空間,當初也是想了半天,因此說,不少東西看似很複雜,其實都是基礎的東西,要好好打實基礎,這纔是萬丈高樓拔地巍峨的前提和保障,學習亦是如是,切忌好高騖遠,應該腳踏實地,一步一步的向前走,並且要不時的總結本身的心得和體會,理論和實踐不斷的相互印證,纔可以走得更遠,看到更美麗的風景。
最後,再次感謝網上無私共享的童鞋們!!!
柔性數組結構成員 收藏
【柔性數組結構成員
C99中,結構中的最後一個元素容許是未知大小的數組,這就叫作柔性數組成員,但結構中的柔性數組成員前面必須至少一個其 他成員。柔性數組成員容許結構中包含一個大小可變的數組。sizeof返回的這種結構大小不包括柔性數組的內存。包含柔性數組成員的結構用malloc ()函數進行內存的動態分配,而且分配的內存應該大於結構的大小,以適應柔性數組的預期大小。】
C語言大全,「柔性數組成員」
【柔性數組結構成員
C99中,結構中的最後一個元素容許是未知大小的數組,這就叫作柔性數組成員,但結構中的柔性數組成員前面必須至少一個其 他成員。柔性數組成員容許結構中包含一個大小可變的數組。sizeof返回的這種結構大小不包括柔性數組的內存。包含柔性數組成員的結構用malloc ()函數進行內存的動態分配,而且分配的內存應該大於結構的大小,以適應柔性數組的預期大小。】
C語言大全,「柔性數組成員」
看看 C99 標準中 靈活數組成員:
結構體變長的妙用——0個元素的數組
有時咱們須要產生一個結構體,實現了一種可變長度的結構。如何來實現呢?
看這個結構體的定義:
typedef struct st_type
{
int nCnt;
int item[0];
}type_a;
(有些編譯器會報錯沒法編譯能夠改爲:)
typedef struct st_type
{
int nCnt;
int item[];
}type_a;
這樣咱們就能夠定義一個可變長的結構,用sizeof(type_a)獲得的只有4,就是sizeof(nCnt)=sizeof(int)那
個0個元素的數組沒有佔用空間,然後咱們能夠進行變長操做了。
C語言版:
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
C++語言版:
type_a *p = (type_a*)new char[sizeof(type_a)+100*sizeof(int)];
這樣咱們就產生了一個長爲100的type_a類型的東西用p->item[n]就能簡單地訪問可變長元素,原理十分簡單
,分配了比sizeof(type_a)多的內存後int item[];就有了其意義了,它指向的是int nCnt;後面的內容,是沒
有內存須要的,而在分配時多分配的內存就能夠由其來操控,是個十分好用的技巧。
而釋放一樣簡單:
C語言版:
free(p);
C++語言版:
delete []p;
其實這個叫靈活數組成員(fleible array member)C89不支持這種東西,C99把它做爲一種特例加入了標準。但
是,C99所支持的是incomplete type,而不是zero array,形同int item[0];這種形式是非法的,C99支持的
形式是形同int item[];只不過有些編譯器把int item[0];做爲非標準擴展來支持,並且在C99發佈以前已經有
了這種非標準擴展了,C99發佈以後,有些編譯器把二者合而爲一。
下面是C99中的相關內容:
6.7.2.1 Structure and union specifiers
As a special case, the last element of a structure with more than one named member may have
an incomplete array type; this is called a flexible array member. With two exceptions, the
flexible array member is ignored. First, the size of the structure shall be equal to the offset
of the last element of an otherwise identical structure that replaces the flexible array member
with an array of unspecified length.106) Second, when a . (or ->) operator has a left operand
that is (a pointer to) a structure with a flexible array member and the right operand names that
member, it behaves as if that member were replaced with the longest array (with the same element
type) that would not make the structure larger than the object being accessed; the offset of the
array shall remain that of the flexible array member, even if this would differ from that of the
replacement array. If this array would have no elements, it behaves as if it had one element but
the behavior is undefined if any attempt is made to access that element or to generate a pointer
one past it.
例如在VC++6裏使用二者之一都能經過編譯而且完成操做,而會產生warning C4200: nonstandard extension
used : zero-sized array in struct/union的警告消息。 而在DEVCPP裏二者一樣可使用,而且不會有警告消息