0数组和灵活数组简介
顾名思义,0数组的意思是数组的长度定义为0。我们一般知道数组的长度至少定义为1才能为其分配实际空间,而定义为0的数组则没有任何空间,但是如果像上面结构体末尾的一个成员被定义为一个零数组,虽然零数组没有分配空间,但是它可以用作偏移量,因为数组名的符号本身代表一个不可修改的地址常量。 柔性数组也称为可扩展性数组,0数组是柔性数组的一种。
因为早期没有引入0长度数组的时候,大家都是通过定长数组和指针来解决的,但是定长数组定义了足够大的缓冲区,使用起来很方便,但是却造成了浪费每次空间指针的方式要求程序员在释放空间时执行多次释放操作,而我们在使用过程中经常会在函数中返回指向缓冲区的指针,并且我们不能保证每个人都理解并遵循我们的准则,因此GNU将其扩展为0 -长度数组。 当使用data[0],即0长度数组时,0长度数组作为数组名,不占用存储空间。 这可以更有效地使用内存。
C99之后又增加了类似的扩展,不过它采用了char[]的形式(所以如果编译时确实需要使用-参数,那么可以将char[0]的类型改为char[],这样编译可以通过,当然你的编译器必须支持C99标准,如果编译器太旧,可能不支持。
0数组的一般用途
首先我们定义一个结构体,然后在结构体的末尾定义一个长度为0的数组,这样结构体就可以变长了。
如下:
// 0长度数组
struct zero_buffer
{
int len;
char data[0];
};
此时data[0]只是一个数组名,不占用存储空间。
这个结构体的大小就是,实际上就是它的成员int的长度,data[0]不占用空间。 (数组名只是一个符号,它不占用任何空间,它只是代表结构体中的一个偏移量,代表一个不可修改的地址常量!)
( ) = (整数)
printf("zero struct length is:%d int length is:%d\n",sizeof(struct zero_buffer),sizeof(int));
零是:4 整数是:4
针对0长度数组的这个特性,很容易构造出我们需要的数据结构,比如缓冲区、数据包等。
结构体定义如上图
假设我们需要设置一条tcp发送的数据,长度为15,数据内容为“Hello My”,那么我们可以这样定义。 其中->data是定义数据的地址,len表示数据的长度。
开辟空间后使用
我们使用的时候只需要开辟一次空间即可。
#define CURR_LENGTH 15
struct zero_buffer *zbuffer = NULL;
// 开辟
if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL)
{
zbuffer->len = CURR_LENGTH;
memcpy(zbuffer->data, "Hello My Friend", CURR_LENGTH);
printf("%d, %s\n", zbuffer->len, zbuffer->data);
}
释放空间
只需释放一次空间
// 销毁
free(zbuffer);
zero_buffer = NULL;
其他实现部分变长数据传输的方法
除了0数组之外,还有使用定长数组和指针数组实现灵活数组的功能。
定长数组
顾名思义,定长数组就是结构体中的定长数组。 这个数组的大小是根据我们定义的最大数据来设置的,是为了防止存储数据时溢出。
定义
// 定长缓冲区
#define MAX_LENGTH 512
struct max_buffer
{
int len;
char data[MAX_LENGTH];
};
但是在使用过程中,比如我要发送512字节的数据,如果我使用定长包,假设定长包的最大长度为1024,那么就会浪费512字节的空间,而不必要的交通也会造成浪费。 如果数组结构是对齐的(这方面的知识详细可以参考我之前的文章) ( ) = (int)+ (char) *
数据包的结构
一般来说,我们返回一个指向缓冲区数据结构的指针。
#define CURR_LENGTH 512
struct max_buffer *mbuffer = NULL;
if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
{
mbuffer->len = CURR_LENGTH;
memcpy(mbuffer->data, "Hello World", CURR_LENGTH);
printf("%d, %s\n", mbuffer->len, mbuffer->data);
}
第一个4字节的部分p->len,作为头(也就是额外的部分),这个头用来描述紧跟在头后面的数据部分的长度,这里是1024,所以前四个字节被赋值为1024(既然我们要构造一个变长的数据包,那么这个包有多长呢?所以我们必须用一个变量来表示这个数据包的长度,这就是len的作用),
而紧接着的内存才是真正的数据部分,通过p->data,最后进行一次()内存拷贝,将要发送的数据填充到这块内存中
释放空间
当释放数据的空间用完后,可以直接释放。
free(mbuffer);
mbuffer = NULL;
使用固定长度数组作为数据缓冲区。 为了避免缓冲区溢出,数组的大小一般设置为足够的空间。 但在实际使用中,达到该长度的数据却很少。 大多数情况下,大部分缓冲区空间都被浪费了。
但使用过程非常简单,数据空间的打开和释放简单,程序员不需要考虑额外的操作
指针数组
它和0数组的区别在于,0数组的最后一个结构体元素定义了一个data[0],而指针数组是需要在结构体中定义的指针数组,而指针数组则不需要具体到结构的最后一个元素。
struct point_buffer
{
char *data;
int len;
};
考虑数组结构对齐(关于这块知识的详细内容可以参考我之前的【数据对齐】()文章),那么数据结构的大小() = (int) + (填充int和char的长度值* type) + ( char *),在我的64位编译环境中,int类型是4byte,char *类型是8byte,所以的长度是8-4,最后的()是。
如果用(())数据对齐修改结构体,则()=(int)(char *),最终计算为。
空间分配使用
#define CURR_LENGTH 1024
struct point_buffer *pbuffer = NULL;
if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
{
pbuffer->len = CURR_LENGTH;
if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL)
{
memcpy(pbuffer->data, "Hello World", CURR_LENGTH);
printf("%d, %s\n", pbuffer->len, pbuffer->data);
}
}
分配内存时需要两个步骤
首先需要为结构体分配一块内存空间;
其次,为结构体中的成员变量分配内存空间。
这样,两次分配的内存是不连续的,需要单独管理。 使用长度为 的数组时,采用一次分配的原则,将需要的全部内存一次性分配给它。
释放了
反之,释放时也一样。
free(pbuffer->data);
free(pbuffer);
pbuffer = NULL;
使用指针结果作为缓冲区,只使用了指针大小的空间,没有使用定长数组,不会造成大量的空间浪费。
但就是在开辟空间的时候,需要为数据字段开辟额外的空间,并且在投射的时候需要显示释放数据字段的空间。 但在实际使用中,往往会在函数中开辟空间,然后返回用户指向的指针。 这时我们不能假设用户了解我们开发的细节,并按照约定的操作释放空间,因此使用起来不方便,甚至会导致内存泄漏
概括:
定长数组使用方便,但浪费空间。 指针形式只是多使用了一个指针空间,不会造成大量的空间浪费,但是需要多次分配和释放。所以最优方案
0数组的优缺点及注意事项
优点:这种方法比在结构体中声明一个指针变量然后动态分配它的效率更高。 由于访问数组内容时不需要间接访问,因此避免了两次内存读取。 另外,0数组不会像定长数组那样造成一定的内存浪费。
缺点:结构体中,数组为0的数组必须声明在最后,使用上有一定的限制。
结语
这是我分享的零数组。 如果您有更好的想法和需求,欢迎加我好友一起交流和分享。
-结尾-