首先,咱们要知道,0长度的数组在ISO C和C++的规格说明书中是不容许的。这也就是为何在VC++2012下编译你会获得一个警告:“warning C4200: 使用了非标准扩展 : 结构/联合中的零大小数组”。html
那么为何gcc能够经过而连一个警告都没有?那是由于gcc 为了预先支持C99的这种玩法,因此,让“零长度数组”这种玩法合法了。关于GCC对于这个事的文档在这里:“Arrays of Length Zero”,文档中给了一个例子(我改了一下,改为能够运行的了):shell
#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看一下:数组
(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是同样的(偏移量为0×0??!!)。可是当咱们输出thisline->contents的时候,你又发现contents的地址是被offset了0×4了的,内容也变成了10个‘a’。(我以为这是一个GDB的bug,VC++的调试器就能很好的显示)bash
咱们继续,若是你sizeof(char[0])或是 sizeof(int[0]) 之类的零长度数组,你会发现sizeof返回了0,这就是说,零长度的数组是存在于结构体内的,可是不占结构体的size。你能够简单的理解为一个没有内容的占位标识,直到咱们给结构体分配了内存,这个占位标识才变成了一个有长度的数组。函数
看到这里,你会说,为何要这样搞啊,把contents声明成一个指针,而后为它再分配一下内存不行么?就像下面同样。布局
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; }
这不同清楚吗?并且也没什么怪异难懂的东西。是的,这也是广泛的编程方式,代码是很清晰,也让人很容易理解。即然这样,那为何要搞一个零长度的数组?有毛意义?!this
这个事情出来的缘由是——咱们想给一个结构体内的数据分配一个连续的内存!这样作的意义有两个好处:spa
第一个意义是,方便内存释放。若是咱们的代码是在一个给别人用的函数中,你在里面作了二次内存分配,并把整个结构体返回给用户。用户调用free能够释放结构体,可是用户并不知道这个结构体内的成员也须要free,因此你不能期望用户来发现这个事。因此,若是咱们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户作一次free就能够把全部的内存也给释放掉。(读到这里,你必定会以为C++的封闭中的析构函数会让这事容易和干净不少)翻译
第二个缘由是,这样有利于访问速度。连续的内存有益于提升访问速度,也有益于减小内存碎片。(其实,我我的以为也没多高了,反正你跑不了要用作偏移量的加法来寻址)
咱们来看看是怎么个连续的,用gdb的x命令来查看:(咱们知道,用struct line {}中的那个char contents[]不占用结构体的内存,因此,struct line就只有一个int成员,4个字节,而咱们还要为contents[]分配10个字节长度,因此,一共是14个字节)
(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[]。
若是用指针的话,会变成这个样子:
(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
上面一共输出了四行内存,其中,
从这里,咱们看到,其中的差异——数组的原地就是内容,而指针的那里保存的是内容的地址。
注:该文转自酷客,选取了其中的关于0长数组的部分,之前0长数组也见过,可是为何要用呢,有什么好处呢,经过该文应该有一个了解!