最近了解到TLV协议后,开始了对其的代码实现,并在期间发现了伸缩性数组这个眼熟却手生的东东,借此机会深挖一下这个伸缩型数组成员(flexible array member)。
struct TLV_BODY // TLV报文
{
short tag; // value类型
short value_length; // 报文长度
char value[0]; // 数据内容
};
注:这个char value[0]; 便是伸缩型数组成员的声明!
1、基本简介:
伸缩型数组成员是在C99标准中新增的一个特性。如果某个结构体含有此特性,那么其最后一个数组成员会具有一些特性:
- 该成员数组不会立刻存在(程序运行时,不会立即为其分配内存空间)
- 程序猿可以利用其创建数目为指定个数的数组
接下来,我们进一步来学习如何使用该数组成员(详情请参考C primer plus 章节14.7.9)。
首先,正确声明一个伸缩型数组成员需要满足以下几条规则:
①伸缩型数组成员必须是结构体的最后一个成员
②结构体中,必须有且至少有一个成员
③声明方法类似于普通函数,只不过它的方括号中不用指定数组元素个数,一般设置为空或0
下面举例说明:
struct var
{
int i;
float j;
double arr[0]; //伸缩型数组成员
//double arr[]; //这样声明也可以
};
声明一个struct var类型的结构体变量时,不能用数组arr[]做任何事,因为系统并没有给该数组分配存储空间。事实上,这和在C99中引入该特性的初衷有关。它并不是让你声明一个struct var类型的结构体变量,而是希望你声明一个指向struct var类型的指针,然后调用malloc()函数来为这个伸缩型数组成员动态分配足够的空间,以存储struct var类型结构体中其他内容和伸缩性数组成员所需的额外存储空间!
例如,我们可以:
struct var *ptr;
ptr = malloc(sizeof(struct var) + 5 * sizeof(double));
这样我们就为结构体的其他内容以及伸缩型数组成员申请了足够的内存,来存储相关数据,并且可以通过指针ptr来访问这些成员。
ptr->i = 15;
ptr->j = 15.6
ptr->arr[4] = 18.5;
2、程序演示
下面以一个具体的程序来演示如何使用伸缩型数组成员。
flex_arr.c 程序代码如下:
#include <stdio.h>
#include <stdlib.h>
struct arr
{
int count;
double average;
double scores[]; //伸缩型数组成员
};
void show_arr(const struct arr *p); //不改变结构体中的内容,故使用关键字const
int main (int argc, char *argv[])
{
int total = 0;
int n = 5;
int i;
struct arr *p1;
//为结构体分配存储空间
p1 = malloc(sizeof(struct arr) + n * sizeof(double));
p1->count = n;
for (i = 0; i < n; i++)
{
p1->scores[i] = 20.0 - i;
total += p1->scores[i];
}
p1->average = total / n;
show_arr(pl);
//释放在堆中动态分配的存储空间
free(p1);
return 0;
}
void show_arr(const struct arr *p)
{
int i;
printf ("Scores : ");
for (i = 0; i < p->count; i++)
{
printf("%g ", p->scores[i]);
}
printf ("\n");
printf ("Average: %g\n", p->average) ;
}
运行结果:
3、补充
对于含有伸缩型数组成员的结构体有一些特殊的处理要求!故在编程时请注意以下三点:
第一、不能用结构体(含有伸缩型数组成员的结构体)进行赋值或copy;
struct var *p1;
struct var *p2;
...
*p1 = *p2; // 不要这样做!
采取这种方法只能拷贝除伸缩型数组成员以外的其他成员。如果要对伸缩型数组成员进行copy的话,可以使用memcpy()函数。
第二、不要以按值方式把这种结构体传递给其他结构体(传值调用)。原因相同,按值传递一个参数与赋值类似。要把结构的地址传递给函数。
第三、不要使用带伸缩型数组成员的结构体作为数组成员或另一个结构体的成员(结构体嵌套)。