關於零長度的數組
首先,我們要知道,0長度的數組在ISO C和C++的規格說明書中是不允許的。這也就是爲什麼在VC++2012下編譯你會得到一個警告:“arning C4200: 使用了非標準擴展 : 結構/聯合中的零大小數組”。
那麼爲什麼gcc可以通過而連一個警告都沒有?那是因爲gcc 爲了預先支持C99的這種玩法,所以,讓“零長度數組”這種玩法合法了。關於GCC對於這個事的文檔在這裏:“Arrays of Length Zero”,文檔中給了一個例子(我改了一下,改成可以運行的了):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include <stdlib.h> #include <string.h> struct
line { int
length; char
contents[0]; // C99的玩法是:char contents[]; 沒有指定數組長度 }; int
main(){ int
this_length=10; struct
line *thisline = ( struct
line *) malloc
( sizeof
( struct
line) + this_length); thisline->length = this_length; memset (thisline->contents,
'a' , this_length); return
0; } |
上面這段代碼的意思是:我想分配一個不定長的數組,於是我有一個結構體,其中有兩個成員,一個是length,代表數組的長度,一個是contents,代碼數組的內容。後面代碼裏的 this_length(長度是10)代表是我想分配的數據的長度。(這看上去是不是像一個C++的類?)這種玩法英文叫:Flexible Array,中文翻譯叫:柔性數組。
我們來用gdb看一下:
1
2
3
4
5
6
7
8
|
( gdb ) p thisline $1 = (struct line *) 0x601010 ( gdb ) p *thisline $2 = {length = 10, contents = 0x601010
"\n" } ( gdb ) p thisline->contents $3 = 0x601014
"aaaaaaaaaa" |
我們可以看到:在輸出*thisline時,我們發現其中的成員變量contents的地址居然和thisline是一樣的(偏移量爲0x0??!!)。但是當我們輸出thisline->contents的時候,你又發現contents的地址是被offset了0x4了的,內容也變成了10個‘a’。(我覺得這是一個GDB的bug,VC++的調試器就能很好的顯示)
我們繼續,如果你sizeof(char[0])或是 sizeof(int[0]) 之類的零長度數組,你會發現sizeof返回了0,這就是說,零長度的數組是存在於結構體內的,但是不佔結構體的size。你可以簡單的理解爲一個沒有內容的佔位標識,直到我們給結構體分配了內存,這個佔位標識才變成了一個有長度的數組。
看到這裏,你會說,爲什麼要這樣搞啊,把contents聲明成一個指針,然後爲它再分配一下內存不行麼?就像下面一樣。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
struct
line { int
length; char
*contents; }; int
main(){ int
this_length=10; struct
line *thisline = ( struct
line *) malloc
( sizeof
( struct
line)); thisline->contents = ( char *)
malloc (
sizeof ( char ) * this_length ); thisline->length = this_length; memset (thisline->contents,
'a' , this_length); return
0; } |
這不一樣清楚嗎?而且也沒什麼怪異難懂的東西。是的,這也是普遍的編程方式,代碼是很清晰,也讓人很容易理解。即然這樣,那爲什麼要搞一個零長度的數組?有毛意義?!
這個事情出來的原因是——我們想給一個結構體內的數據分配一個連續的內存!這樣做的意義有兩個好處:
第一個意義是,方便內存釋放。如果我們的代碼是在一個給別人用的函數中,你在裏面做了二次內存分配,並把整個結構體返回給用戶。用戶調用free可以釋放結構體,但是用戶並不知道這個結構體內的成員也需要free,所以你不能指望用戶來發現這個事。所以,如果我們把結構體的內存以及其成員要的內存一次性分配好了,並返回給用戶一個結構體指針,用戶做一次free就可以把所有的內存也給釋放掉。(讀到這裏,你一定會覺得C++的封閉中的析構函數會讓這事容易和乾淨很多)
第二個原因是,這樣有利於訪問速度。連續的內存有益於提高訪問速度,也有益於減少內存碎片。(其實,我個人覺得也沒多高了,反正你跑不了要用做偏移量的加法來尋址)
我們來看看是怎麼個連續的,用gdb的x命令來查看:(我們知道,用struct line {}中的那個char contents[]不佔用結構體的內存,所以,struct line就只有一個int成員,4個字節,而我們還要爲contents[]分配10個字節長度,所以,一共是14個字節)
1
2
3
|
( gdb ) x
/14b
thisline 0x601010: 10 0 0 0 97 97 97 97 0x601018: 97 97 97 97 97 97 |
從上面的內存佈局我們可以看到,前4個字節是 int length,後10個字節就是char contents[]。
如果用指針的話,會變成這個樣子:
1
2
3
4
5
6
|
( gdb ) x
/16b
thisline 0x601010: 1 0 0 0 0 0 0 0 0x601018: 32 16 96 0 0 0 0 0 ( gdb ) x
/10b
this->contents 0x601020: 97 97 97 97 97 97 97 97 0x601028: 97 97 |
上面一共輸出了四行內存,其中,
- 第一行前四個字節是 int length,第一行的後四個字節是對齊。
- 第二行是char* contents,64位系統指針8個長度,他的值是0x20 0x10 0x60 也就是0x601020。
- 第三行和第四行是char* contents指向的內容。
從這裏,我們看到,其中的差別——數組的原地就是內容,而指針的那裏保存的是內容的地址。
from: http://coolshell.cn/articles/11377.html