Part1
轉自: http://my.oschina.net/u/176416/blog/33054
長度為0的數組在標准c和c++中是不允許的,如果使用長度為0的數組,編譯時會產生錯誤,提示數組長度不能為0。但在GNUc中,這種用法卻是合法的。它的最典型的用法就是位於數組中的最后一項,如上面所示,這樣做主要是為了方便內存緩沖區的管理。如果你將上面的長度為0的數組換為指針,那么在分配內存時,需采用兩步:首先,需為結構體分配一塊內存空間;其次再為結構體中的成員變量分配內存空間。這樣兩次分配的內存是不連續的,需要分別對其進行管理。當使用長度為的數組時,則是采用一次分配的原則,一次性將所需的內存全部分配給它。相反,釋放時也是一樣的。
對於長度為0的數組,在gcc手冊中,有如下一段代碼片段:
1 struct line { 2 int length; 3 char contents[0]; 4 }; 5 6 struct line *thisline = (struct line *)malloc (sizeof (struct line) + this_length); 7 thisline->length = this_length;
這段代碼的主要含義是定義了一個結構體,並對其進行初始化,上面結構體的第二個成員變量contents[0]事實上是不占內存空間的,因此整個結構體的長度sizeof(struct line)為4。當采用malloc為其申請內存空間時,如上所示,申請了一段長度為結構體長度加可變長度的內存空間給結構體類型的指針,這時contents就指向申請的可變長度的內存空間。由於是一次申請的,所以這段可變長度的內存空間和前面的結構體長度的內存空間是連續的。對於這段可變長度的內存空間,可以采用數組的方式對其進行訪問。對於整個結構體,當不再使用時,可以使用free函數一次性對其進行釋放,而不必像指針那樣分別釋放。
下面舉例進行說明:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #define LENGTH 10 4 5 struct test1 6 { 7 int a; 8 int *b; 9 }__attribute((packed)); 10 11 struct test2 12 { 13 int a; 14 int b[0]; 15 }__attribute((packed)); 16 17 struct test3 18 { 19 int a; 20 int b[1]; 21 }__attribute((packed)); 22 23 int main() 24 { 25 struct test1 *var1; 26 struct test2 *var2; 27 struct test3 *var3; 28 int i; 29 30 printf("the length of struct test1:%d\n",sizeof(struct test1)); 31 printf("the length of struct test2:%d\n",sizeof(struct test2)); 32 printf("the length of struct test3:%d\n",sizeof(struct test3)); 33 34 var1=(struct test1*)malloc(sizeof(struct test1)); 35 var1->a=1; 36 var1->b=(int *)malloc(sizeof(int)); 37 *var1->b=1; 38 printf("\nvar1->a=%d,*(var1->b)=%d\n",var1->a,*var1->b); 39 40 var2=(struct test2*)malloc(sizeof(struct test2)+sizeof(int)*LENGTH); 41 var2->a=2; 42 printf("\nvar2->a=%d\n",var2->a); 43 for(i=0;i<LENGTH;i++) 44 { 45 var2->b[i]=i; 46 printf("var2->b[i]=%d\t",var2->b[i]); 47 } 48 printf("\n\n"); 49 50 var3=(struct test3*)malloc(sizeof(struct test3)); 51 var3->a=3; 52 (var3->b)[0]=3; 53 printf("var3->a=%d,(var3->b)[0]=%d\n",var3->a,(var3->b)[0]); 54 55 56 free(var1->b); 57 free(var1); 58 free(var2); 59 free(var3); 60 61 }
這段程序的運行結果如下圖所示:
從上面的結果可以看出:
l 長度為0的數組並不占有內存空間,而指針方式需要占用內存空間。
l 對於長度為0數組,在申請內存空間時,采用一次性分配的原則進行;對於包含指針的結構體,才申請空間時需分別進行,釋放時也需分別釋放。
l 對於長度為0的數組的訪問可采用數組方式進行。
Par2
轉自:http://lijingzu7.blog.163.com/blog/static/21102828201021092641494/
在linuxforum上看見有人討論數組大小為0的理解,覺得不錯,特摘錄下來,以備參考:
Q:數組大小為0應該怎么理解?
比如:
1 struct page *page[0]; 2 unsigned long private[0] ____cacheline_aligned; 3 4 //A:一個很好的例子就是 5 struct unix_address 6 { 7 atomic_t refcnt; 8 int len; 9 unsigned hash; 10 struct sockaddr_un name[0]; 11 }; 12 13 #define UNIX_PATH_MAX 108 14 15 struct sockaddr_un { 16 sa_family_t sun_family; /* AF_UNIX */ 17 char sun_path[UNIX_PATH_MAX]; /* pathname */ 18 };
其中name實際上指向unix_address結構之后的空間。
好處就是sockaddr_un是變長的,為了不浪費空間,就這樣定義。到實際分配unix_address結構的時候按實際的地址大小一並分配空間,那樣就可以通過unix_address->name來訪問實際的地址。
這樣做的具體的優點:
1 struct unix_address 2 { 3 atomic_t refcnt; 4 int len; 5 unsigned hash; 6 void *name; 7 };
1。name占用空間(4字節)
2。分配空間的時候可以一起分配,但是需要把p->name設置為=p++,訪問name的值時需要指針中
轉一下
而
1 struct unix_address 2 { 3 atomic_t refcnt; 4 int len; 5 unsigned hash; 6 struct sockaddr_un name[0]; 7 };
沒有上面兩個缺點
1。它不占用結構的空間
2。如果訪問某個p->name,卻可以訪問緊接p的內存空間
利用上面兩個特性,就可以根據實際的unix域地址大小來一起分配空間,通過p->name訪問地址
-------------------------------------------------------------------
| |實際地址|
-------------------------------------------------------------------
^ ^
| |
p name
它可以用來定義一個變長的結構體,比如你可以動態分配這個結構體,並把分配的長度等於sizeof(struct) + len,這樣len這么長的內存就可以直接用最后的那個沒有實際空間的指針來引用了。
有人提到這樣用malloc函數開辟空間后malloc(sizeof(struct) + len)。再用free釋放時會不會出問題?
找到的資料來看,都認為不會:
(1)如果你有一個malloc到的指針,可以看一下它所指的地址,在前面一點有一個指明分配的空間的大小的數字,這個就是所謂的“釋放內存大小的量”,如果你不相信的話可以用vc試試,我的確看到過。
但是這個數字一般來說不是你malloc或者realloc時候傳遞的size參數,因為堆分配策略中分配的堆內存不能過於零散,所以一般實際分配的長度是某些特定的值:4B,12B,24B,36B……等等。
樓主可以自己再驗證一下,關於嵌入式系統,應該比普通的系統更加嚴格。
(2)free釋放空間 的大小存放在什么地方是由堆的實現決定的.
如sandrowjw所說,如果堆是采用的buddy算法,其釋放大小由內存起始地址決定了
如果是其它策略,則這個值可能會存放在指針前的幾個字節,也可能存在某個固定的內存區域(只能被堆的實現所訪問).
(3)free只傳一個指針,是怎么知道要釋放的內存大小的呢,有學習過操作系統內存管理的人可能知道,我們申請一塊內存的時候,(以下可能不同系統實現不一樣):
申請成功,那么系統給我們是一個指針的地址,這個地址里面的某個地方存放着內存塊的描述符(包括內存類型和大小)這樣,free的時候系統會先讀取這個描述符,返回再釋放,所以能釋放掉所有我們申請的內存,具體詳細的還是的學習學習操作系統原理。
當我們得到一個指針的時候,我們可以對其類型做強制轉換,這都不影響內存里面的實際空間大小,釋放的時候系統還是能安全釋放。
可是如果我們對指針地址做了修改,比如:
char *p = (char *)malloc(sizeof(char) * LEN);
int *i = (int *)p;
那么free(i),OK,沒問題;
如果
i = (int *)p + 1;
free(i); 那么掛了,呵呵!
就是這樣了,地址被你改變了,找不到內存塊的描述符了,也就不知道內存的類型和大小,自然也就掛了!
不過這個存放描述符的地址每個系統都不一樣(也有可能有一樣的,^_^),里面的具體內容也不一樣;
Part3
轉自:http://www.cnblogs.com/winkyao/archive/2012/02/14/2351885.html
其實很早在看LINUX下就看到這個東西,后來在MFC內存池里同樣也看到了類似的東西,還依照MFC寫過一個類似的小內存池,(MFC用的是return this + 1)后來在李先靜的《系統程序員成長計划》里看到了類似的定義,於是心里想着總結一下,結果發現網上已經有牛人總結的很好了,於是乎就轉了過來,謝謝你們的分享,這是我前進的動力!
同時,需要引起注意的:ISO/IEC 9899-1999里面,這么寫是非法的,這個僅僅是GNU C的擴展,gcc可以允許這一語法現象的存在。但最新的C/C++不知道是否可以,我沒有測試過。(C99允許。微軟的VS系列報一個WARNING,即非常的標准擴展。)
結構體最后使用0或1的長度數組的原因,主要是為了方便的管理內存緩沖區,如果你直接使用指針而不使用數組,那么,你在分配內存緩沖區時,就必須分配結構體一次,然后再分配結構體內的指針一次,(而此時分配的內存已經與結構體的內存不連續了,所以要分別管理即申請和釋放)而如果使用數組,那么只需要一次就可以全部分配出來,(見下面的例子),反過來,釋放時也是一樣,使用數組,一次釋放,使用指針,得先釋放結構體內的指針,再釋放結構體。還不能顛倒次序。
其實就是分配一段連續的的內存,減少內存的碎片化。
| 標題 | 結構體最后的長度為0或者1的數組 選擇自 googol4u 的 Blog |
在Linux系統里,/usr/include/linux/if_pppox.h里面有這樣一個結構:
1 struct pppoe_tag { 2 __u16 tag_type; 3 __u16 tag_len; 4 char tag_data[0]; 5 } __attribute ((packed));
最后一個成員為可變長的數組,對於TLV(Type-Length-Value)形式的結構,或者其他需要變長度的結構體,用這種方式定義最好。使用起來非常方便,創建時,malloc一段結構體大小加上可變長數據長度的空間給它,可變長部分可按數組的方式訪問,釋放時,直接把整個結構體free掉就可以了。例子如下:
1 struct pppoe_tag *sample_tag; 2 __u16 sample_tag_len = 10; 3 sample_tag = (struct pppoe_tag *)malloc(sizeof(struct pppoe_tag)+sizeof(char)*sample_tag_len); 4 sample_tag->tag_type = 0xffff; 5 sample_tag->tag_len = sample_tag_len; 6 sample_tag->tag_data[0]=.... 7 ... 8 //釋放時, 9 free(sample_tag)
是否可以用 char *tag_data 代替呢?其實它和 char *tag_data 是有很大的區別,為了說明這個問題,我寫了以下的程序:
1 //例1:test_size.c 2 struct tag1 3 { 4 int a; 5 int b; 6 }__attribute ((packed)); 7 8 struct tag2 9 { 10 int a; 11 int b; 12 char *c; 13 }__attribute ((packed)); 14 struct tag3 15 { 16 int a; 17 int b; 18 char c[0]; 19 }__attribute ((packed)); 20 struct tag4 21 { 22 int a; 23 int b; 24 char c[1]; 25 }__attribute ((packed)); 26 int main() 27 { 28 struct tag2 l_tag2; 29 struct tag3 l_tag3; 30 struct tag4 l_tag4; 31 32 memset(&l_tag2,0,sizeof(struct tag2)); 33 memset(&l_tag3,0,sizeof(struct tag3)); 34 memset(&l_tag4,0,sizeof(struct tag4)); 35 36 printf("size of tag1 = %d\n",sizeof(struct tag1)); 37 printf("size of tag2 = %d\n",sizeof(struct tag2)); 38 printf("size of tag3 = %d\n",sizeof(struct tag3)); 39 40 printf("l_tag2 = %p,&l_tag2.c = %p,l_tag2.c = %p\n",&l_tag2,&l_tag2.c,l_tag2.c); 41 printf("l_tag3 = %p,l_tag3.c = %p\n",&l_tag3,l_tag3.c); 42 printf("l_tag4 = %p,l_tag4.c = %p\n",&l_tag4,l_tag4.c); 43 exit(0); 44 }
__attribute ((packed)) 是為了強制不進行4字節對齊,這樣比較容易說明問題。
程序的運行結果如下:
1 size of tag1 = 8 2 size of tag2 = 12 3 size of tag3 = 8 4 size of tag4 = 9 5 l_tag2 = 0xbffffad0,&l_tag2.c = 0xbffffad8,l_tag2.c = (nil) 6 l_tag3 = 0xbffffac8,l_tag3.c = 0xbffffad0 7 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個字節的指針變量外,用起來會比較不方便:
方法一,創建時,可以首先為struct pppoe_tag分配一塊內存,再為tag_data分配內存,這樣在釋放時,要首先釋放tag_data占用的內存,再釋放pppoe_tag占用的內存;
方法二,創建時,直接為struct pppoe_tag分配一塊struct pppoe_tag大小加上tag_data的內存,從例一的420行可以看出,tag_data的內容要進行初始化,要讓tag_data指向strct pppoe_tag后面的內存。
1 struct pppoe_tag { 2 __u16 tag_type; 3 __u16 tag_len; 4 char *tag_data; 5 } __attribute ((packed)); 6 7 struct pppoe_tag *sample_tag; 8 __u16 sample_tag_len = 10;
方法一:
1 sample_tag = (struct pppoe_tag *)malloc(sizeof(struct pppoe_tag)); 2 sample_tag->tag_len = sample_tag_len; 3 sample_tag->tag_data = malloc(sizeof(char)*sample_tag_len); 4 sample_tag->tag_data[0]=... 5 //釋放時: 6 free(sample_tag->tag_data); 7 free(sample_tag);
方法二:
1 sample_tag = (struct pppoe_tag *)malloc(sizeof(struct pppoe_tag)+sizeof(char)*sample_tag_len); 2 sample_tag->tag_len = sample_tag_len; 3 sample_tag->tag_data = ((char *)sample_tag)+sizeof(struct pppoe_tag); 4 sample_tag->tag_data[0]=... 5 //釋放時: 6 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]本質上是否有區別?我們可以通過一個例子來說明。
例二:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main() 5 { 6 char a[10]; 7 char *b; 8 9 a[2]=0xfe; 10 b[2]=0xfe; 11 exit(0); 12 }
編譯后,用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]本質上是不同的。
但當數組作為參數時,和指針就沒有區別了。
1 int do1(char a[],int len); 2 int do2(char *a,int len);
這兩個函數中的a並無任何區別。都是實實在在存在的指針變量。
順便再說一下,對於struct pppoe_tag的最后一個成員的定義是char tag_data[0],某些編譯器不支持長度為0的數組的定義,在這種情況下,只能將它定義成char tag_data[1],使用方法相同。
在openoffice的源代碼中看到如下數據結構,是一個unicode字符串結構,他的最后就用長度為1數組,可能是為了兼容或者跨編譯器。
1 typedef struct _rtl_uString 2 { 3 sal_Int32 refCount; 4 sal_Int32 length; 5 sal_Unicode buffer[1]; 6 } rtl_uString; 7 //這是不定長字符串。大概意思是這樣: 8 9 rtl_uString * str = malloc(256); 10 str->length = 256; 11 //str->buffer現在就指向一個長度為256 - 8的緩沖區
總結:通過上面的轉載的文章,可以清晰的發現,這種方法的優勢其實就是為了簡化內存的管理,我們假設在理想的內存狀態下,那么分配的內存空間,可以是按序下來的(當然,實際因為內存碎片等的原因會不同的)我們可以利用最后一個數組的指針直接無間隔的跳到分配的數組緩沖區,這在LINUX下非常常見,在WINDOWS下的我只是在MFC里見過類似的,別的情況下記不清楚了,只記得MFC里的是這么講的,可以用分配的結構體的指針(this)直接+1(詳細的方法請看我的博客:CE分類里的:內存池技術的應用和詳細說明),就跳到實際的內存空間,當初也是想了半天,所以說,很多東西看似很復雜,其實都是基礎的東西,要好好打實基礎,這才是萬丈高樓拔地巍峨的前提和保障,學習亦是如是,切忌好高騖遠,應該腳踏實地,一步一步的向前走,而且要不時的總結自己的心得和體會,理論和實踐不斷的相互印證,才能夠走得更遠,看到更美麗的風景。
最后,再次感謝網上無私共享的童鞋們!!!
柔性數組結構成員 收藏
【柔性數組結構成員
C99中,結構中的最后一個元素允許是未知大小的數組,這就叫做柔性數組成員,但結構中的柔性數組成員前面必須至少一個其 他成員。柔性數組成員允許結構中包含一個大小可變的數組。sizeof返回的這種結構大小不包括柔性數組的內存。包含柔性數組成員的結構用malloc ()函數進行內存的動態分配,並且分配的內存應該大於結構的大小,以適應柔性數組的預期大小。】
C語言大全,“柔性數組成員”
【柔性數組結構成員
C99中,結構中的最后一個元素允許是未知大小的數組,這就叫做柔性數組成員,但結構中的柔性數組成員前面必須至少一個其 他成員。柔性數組成員允許結構中包含一個大小可變的數組。sizeof返回的這種結構大小不包括柔性數組的內存。包含柔性數組成員的結構用malloc ()函數進行內存的動態分配,並且分配的內存應該大於結構的大小,以適應柔性數組的預期大小。】
C語言大全,“柔性數組成員”
看看 C99 標准中 靈活數組成員:
結構體變長的妙用——0個元素的數組
有時我們需要產生一個結構體,實現了一種可變長度的結構。如何來實現呢?
看這個結構體的定義:
1 typedef struct st_type 2 { 3 int nCnt; 4 int item[0]; 5 }type_a; 6 //(有些編譯器會報錯無法編譯可以改成:) 7 typedef struct st_type 8 { 9 int nCnt; 10 int item[]; 11 }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里兩者同樣可以使用,並且不會有警告消息
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/todototry/archive/2007/04/11/1560458.aspx

