深入浅出C语言中的柔性数组
问题引出
在日常的编程中,有时候需要在结构体中存放一个长度动态的字符串,一般的做法,是在结构体中定义一个指针成员,这个指针成员指向该字符串所在的动态内存空间,例如:
typedef struct
{
WORD usMsgID;
WORD usMsgLen;
char *pData;//pData指向字符串
}COMMMON_DEF_STRU
针对这种普通形式的结构体,我们很容易写出如下代码:
COMMMON_DEF_STRU* pCommonStru = (COMMMON_DEF_STRU*)malloc(sizeof(COMMMON_DEF_STRU));
pCommonStru->usMsgID = 0x1234;
pCommonStru->usMsgLen = sizeof(szInfo);
//创建字符串存储buffer
pCommonStru->pData = (char*)malloc(pCommonStru->usMsgLen);
//拷贝字符串到pDatabuffer
memcpy_s(pCommonStru->pData,pCommonStru->usMsgLen,szInfo,pCommonStru->usMsgLen);
//数据操作
printf("print data: %s\n",pCommonStru->pData);
//释放字符串所指的内存
SAFE_FREE(pCommonStru->pData);
//释放整个结构体的内存,需要释放两次,而且有先后顺序关系
SAFE_FREE(pCommonStru);
从上面代码可以看出,使用设计我们会进行两次的内存分配和内存释放,释放过程需要注意前后顺序关系,整个过程相对麻烦一点;使用这种方法的结构体设计,容易造成数据(字符串)与结构体本身是分离的,对后续释放造成麻烦。如果把字符串跟结构体直接连在一起,不是更好吗?
解决方案
针对以上问题 可以有一个更优的解决方案即,在结构体的尾部放置一个0长度的数组是一个绝妙的解决方案,我们既能直接引用该字符串,又不占用结构体的空间。不过,C/C++标准规定不能定义长度为0的数组,因此,有些编译器就把0长度的数组成员作为自己的非标准扩展。
在讲述柔性数组成员之前,首先要介绍一下不完整类型(incomplete type)。不完整类型是这样一种类型,它缺乏足够的信息例如长度去描述一个完整的对象,它的出现反映了C程序员对精炼代码的极致追求,这种代码结构产生于对动态结构体的需求。
鉴于这种代码结构所产生的重要作用,C99甚至把它收入了标准中。C99使用不完整类型实现柔性数组成员;其定义如下:
柔性数组:在结构体中的最后一个元素允许是未知大小的数组,这样的数组做柔性数组(flexible array)成员(也叫伸缩性数组成员)。
包含柔性数组的结构体设计:
typedef struct
{
WORD usMsgID;
WORD usMsgLen;
char pData[0];//柔性数组
}FLEXIBLE_DEF_STRU;
有些编译器会报错无法编译可以改成:
typedef struct
{
WORD usMsgID;
WORD usMsgLen;
char pData[];//大小可变
}FLEXIBLE_DEF_STRU;
针对这种包含柔性数组的结构体设计,有以下几点注意事项:
结构体中的柔性数组成员前面必须至少一个其他成员。
柔性数组必须是结构体的最后一个成员。
柔性数组成员只作为一个符号地址存在,sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。
柔性数组成员不仅可以用于字符数组,还可以是元素为其它类型的数组。
通过如下表达式给结构体分配内存:
char szDataInfo[] = "hello world";
//malloc内存大小为结构体大小+数据大小,包含‘\0’
FLEXIBLE_DEF_STRU *pTest = (FLEXIBLE_DEF_STRU *)malloc(sizeof(FLEXIBLE_DEF_STRU)+sizeof(szDataInfo));
pData就是一个柔性数组成员,如果把pTest 指向的动态分配内存看作一个整体,pData就是一个长度可以动态变化的结构体成员,指向数据区的首地址,数据区的大小在分配时进行动态申请,柔性一词来源于此。
柔性数组的使用请看下面的例子:
char szInfo[] = "hello world";
FLEXIBLE_DEF_STRU *pTest = (FLEXIBLE_DEF_STRU *)malloc(sizeof(FLEXIBLE_DEF_STRU)+sizeof(szInfo));
pTest->usMsgID = 0x1234;
pTest->usMsgLen = sizeof(szInfo);
//若拷贝的内容是字符串,注意'\0'也需要被拷贝走,
//所以使用sizeof(),不能是strlen()
memcpy_s(pTest->pData,pTest->usMsgLen,szInfo, sizeof(szInfo));
printf("%s\n",pTest->pData);
//只需要释放一次内存
SAFE_FREE(pTest);
对于包含有柔性数组的结构体,我们对内存的申请和释放只需要一次。因为我们的数据区buffer紧随在结构体之后,数据和结构体在同一个内存块中,因此我们只需要释放一次,示意图如下:
我们应当尽量使用标准形式,在非C99的场合,可以使用指针方法。需要说明的是:C89不支持这种东西,C99把它作为一种特例加入了标准。但是,C99所支持的是incomplete type,而不是zero array,形同int a[0];这种形式是非法的,C99 支持的形式是形同int a[];只不过有些编译器把int a[0];作为非标准扩展来支持,而且在C99 发布之前已经有了这种非标准扩展了,C99 发布之后,有些编译器把两者合而为一了。
参考资料:
http://blog.csdn.net/ce123_zhouwei/article/details/8973073