在前面的文章中的dnw源代码,使用了这样一个结构体:
struct download_buffer {
uint32_t load_addr; /* load address */
uint32_t size; /* data size */ //size=地址(4位)+大小(4位)+数据+校验(2位)
uint8_t data[0];//0长度数组,指向数据
/* uint16_t checksum; */ //数组后紧接着的两位是校验位
};
其中data[0]是一个0长度数组,用于柔性拓展结构体。
分配内存空间:
buffer = alloc_buffer(file_stat.st_size);
也就是:
buffer = malloc(data_size + sizeof(struct download_buffer) + 2);
data_size是结构体成员buffer->data的长度,使用sizeof(struct download_buffer)取得结构体长度时,不计算
data成员的长度,这样就实现了一个可以动态调节大小的结构体,根据所需要的空间分配给data成员空间。
再来看一个网上的例子:
定义一个结构体
typedef struct user_def
{
char * name;
int length;
char bytes[0];
} user_def_t;
在这里我们使用sizeof(bytes)会发现结果等于0,这个bytes就是一个零长度的柔性数组,使用方法如下:
int alloc_user_def_t(user_def_t * p, int length)
{
p = (user_def_t)malloc(sizeof(user_def_t) + length);
if (NULL == p)
{
return -1;
}
p->name = NULL;
p->length = length;
memset(p->bytes, 0, length);
return 0;
}
这样我们就将length长度的0字符写进bytes数组,相当与一个附件附在结构体后面,可以通过这个数组访问。
再看一个网上例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
struct helloworld_t
{
int num;
char helloworld[0];//主要是用来得到一个数组的地址,再由数组的个数来访问
};
int main()
{
struct helloworld_t *p;
unsigned int size = sizeof(struct helloworld_t) + strlen("Hello World!\n") + 1;
p = (struct helloworld_t *) malloc(size);
assert(p!=NULL);
memcpy(p, "\x01\x00\x00\x00Hello World!\n", size);//\x01\x00\x00\x00四字节,就是给num赋值
//在内存中高8位和低8位反一下...
// printf("%d\n",p->num);
while (p->num--)
{
printf(p->helloworld);
}
//printf("%d \n", sizeof(helloworld_t));
free((void *)p);
return 0;
}
打印结果是:Hello World!
C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结
构中的柔性数组成员前面必须至少一个其他成员。 柔性数组成员允许结构中包含一个大小可
变的数组。sizeof返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用
malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组
的预期大小。
在定义这个结构体的时候,结构体的大小就已经确定不包含柔性数组的内存大小。
柔性数组只是编外人员,不占结构体的编制。只是说在使用柔性数组时需要把它当作结
构体的一个成员,仅此而已。再说白点,柔性数组其实与结构体没什么关系,只是“挂羊头
卖狗肉”而已,算不得结构体的正式成员。
需要说明的是:C89不支持这种东西,C99把它作为一种特例加入了标准。但是,C99所
支持的是 incomplete type,而不是 zero array,形同 int item[0];这种形式是非法的,C99支持
的形式是形同 int item[];只不过有些编译器把 int item[0];作为非标准扩展来支持,而且在C99
发布之前已经有了这种非标准扩展了,C99发布之后,有些编译器把两者合而为一了。当然,上面
既然用 malloc函数分配了内存,肯定就需要用free函数来释放内存:
free(p);
举个例子:
typedef struct st_type
{
int i;
int a[0];
}type_a;
GNU的编译器可以通过编译,有些编译器会报错无法编译(例如VS系列)可以改成:
typedef struct st_type
{
int i;
int a[];
}type_a;
这样我们就可以定义一个可变长的结构体,用 sizeof(type_a)得到的只有 4,就是
sizeof(i)=sizeof(int)。那个 0 个元素的数组没有占用空间,而后我们可以进行变长操作了。通
过如下表达式给结构体分配内存:
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
这样我们为结构体指针 p 分配了一块内存。用 p->a[n]就能简单地访问可变长元素(p->a[0]~p->[99])。
但是这时候我们再用 sizeof(*p)测试结构体的大小,发现仍然为 4。
那么,为什么不在结构体里使用指针成员来指向该字符串所在的动态内存空间呢?
例如:
struct ptest
{
int a;
double b;
char *c;
};
指针成员c指向字符串。这种方法会造成字符串与结构体分离,malloc和free对内存管理产生碎片,也不利于操作。
如果我们把字符串和结构体连接在一起,字符串位置紧接着结构体,这样修改代码:
char Str[] = "hello world";
struct ptest *Test = (struct ptest*)malloc(sizeof(struct ptest)+strlen(Str)+1);//+1是因为strlen不计算结束符
strcpy(Test+1,Str)
(char*)(Test+1)就是字符串“hello world”的地址了。c就成了多余的东西,可以去掉。然而,直接使用(char*)(Test+1)很不方便。于是,这种既能直接引用字符串,还不占用结构体空间,能灵活分配数据域大小的要求,柔性数组就能够满足。
在这个结构体中,c是柔性数组成员,如果把Test指向的动态内存分配看作一个整体,c就是一个长度可以变化的柔性数组,c在结构体中长度为0,不占用Test的空间,Test->c就是“hello world”的首地址。