C99标准中加入了柔性数组(flexible array ),也叫伸缩数组。我在linux的源码里面发现多处都用到了这种语法。如(已手动翻译为C++语法以举例,原来代码是C语言):

1
2
3
4
5
6
7
8
#pragma pack(push, 1)
struct my_data
{
int len;
int value;
char extra[0];
};
#pragma pack(pop)

为了更好地说明问题,我把内存对齐强制为1字节,对my_data结构体进行sizeof()会得到8,最后一个成员并不占结构的大小,8字节仅来源于前面的两个int成员的长度。

在C99中,柔性数组并不是被定义的一项标准,这种用法属于不完整数组类型(incomplete array type),它和我们平时对一种类型进行声明而不对它进行定义是一样的性质。

上面例子列举的char extra[0]并不符合标准,因为C99支持的并非是零长度的数组,而是不完整类型。目前大多数编译器都把它当作了非标准扩展,支持这种写法,但为了移植性更好,我建议使用char extra[],这样可读性更高。

extra实际上是一个可变长的数组,在没给它赋值之前,它毫无意义,但一旦给它分配内存,它的作用就不言而喻。

1
2
3
4
5
6
7
8
#pragma pack(push, 1)
struct Header
{
int len;
int value;
char body[];
};
#pragma pack(pop)

body成员并没有占用结构体的空间,它相当于一个占位符,仅代表了一个内存地址。

1
2
3
//分配内存
void* p = (void*)new char[sizeof(Header) + size]();
delete [] p;

分配内存的代码十分简单。需要注意的是,我们分配的大小应该是比header更大的空间,结构体的内存分布是连续的,因此超出sizeof(Header)的部分就是body的长度。如果要访问body的内容,那么我们直接拿到body的指针即可,也可以通过进行指针运算,把p指针的地址偏移sizeof(Header)的值,拿到的就是body的地址。

1
void *body_pointer = (void*)((int)p + sizeof(Header));

有两点注意事项:
其一,分配的内存需要删除,因为我们分配的时候用了new char的方法,相当于分配了一个char数组,因此需要使用显式使用delete []方法删除。当然,如果用malloc方法分配的内容则用free();
其二,如果你的结构有构造函数,那么在分配内存的时候记得显式调用构造函数。如:void p = (void)new charsize;

它的用途最明显的例子:
当我们在编写网络通信程序时,一般会定义一个用于通信的数据结构,这个结构由一个固定长度的header和不定长度的body组成。应用了该技巧,我们在定义Header结构的时候,就可以写上一个body占位符,这样我们既不会影响Header的长度,也可以很方便地通过body成员取得body的内容。只是,如果你觉得使用指针会让代码可读性更好的话,也可以用指针,但这样一来,结构体就会多出了储存一个指针所需要的长度。