C语言柔性数组
柔性数组的概念
柔性数组(flexible array member)也叫伸缩性数组成员,这种结构产生与对动态结构体的去求。在日常编程中,有时需要在结构体中存放一个长度是动态的字符串(也可能是其他数据类型),一般的做法,实在结构体中定义一个指针成员,这个指针成员指向该字符串所在的动态内存空间。
在通常情况下,如果想要高效的利用内存,那么在结构体内部定义静态的数组是非常浪费的行为。其实柔性数组的想法和动态数组的想法是一样的。
柔性数组用来在结构体中存放一个长度动态的字符串。
普通的方式
其实不用柔性数组我们一样可以做到:在结构体中定义一个方法,在方法中动态地将指针指向动态数组
#include <string.h> #include <stdlib.h> typedef struct Test { int a; char *p; } Test; int main() { char *str = (char *)malloc(sizeof(char) * 10); strcpy(str, "hello"); Test *t = (Test *)malloc(sizeof(Test)); t->p = str; printf("%s\n", (t->p)); printf("Address:\n"); printf("t\t %p\n", t); printf("t.a\t %p\n", &(t->a)); printf("t.p\t %p\n", (t->p)); free(t); free(t->p); //还需要显式的释放p所指的内存 return 0; }
运行结果:
上面的代码的确是可以完成我们想要的结果。我们看了一下指针p和数组的起始地址。我们可以看到动态数组的内存块和字符串的内存是两块不一样的内存。
但是,我们在释放空间时,需要显式地释放指针p引用的内存空间,不然会出现内存泄露的情况。
使用柔性数组的方式
从C99开始便支持了不完整类型实现柔性数组成员。为什么使用不完整类型呢?
int a[] = {10};
看到这个声明语句,我们发现a[]其实就是个数组记号,不完整类型,由于赋值语句,所以在编译时便确定了数组的大小,是一个完整的数组类型。
在结构体中便利用不完整类型在运行对动态的数组进行指明。
C99标准的定义如下
struct Test{ int a; char p[]; // 不只是char类型,其他类型同样也是可以 }
由于声明内存连续性的关系,柔性数组成员必须定义在结构体的最后一个,并且不能是唯一的成员。
我们再来看一看整个结构体(包含数组内存的分布情况)
#include <string.h> #include <stdlib.h> typedef struct Test { int a; char p[]; } Test; int main() { Test *t=(Test*)malloc(sizeof(Test)+sizeof(char)*(10+1)); strcpy(t->p,"hello"); printf("%s\n", (t->p)); printf("Address:\n"); printf("t\t %p\n", t); printf("t.a\t %p\n", &(t->a)); printf("t.p\t %p\n", (t->p)); free(t); //只需要释放一次内存 return 0; }
再运行结果:
由运行结果就可以看出,整个结构体是连续的,并且释放结构体的方式也非常简单直接对结构体指针进行释放。
【注】由于这个是C99的标准,在ISO C和C++的规格说明书中是不允许的。在vs下使用0长度的数组可能会得到一个警告。
然而gcc, clang++预先支持了C99的玩法,所以在Linux下编译无警告
进一步认识柔性数组
现有一个程序
#include<stdio.h> typedef struct _SoftArray{ int len; int array[]; }SoftArray; int main() { int len = 10; printf("The struct's size is %d\n",sizeof(SoftArray)); }
运行结果:
我们可以看出,_SoftArray结构体的大小是4,显然,在32位操作系统下一个int型变量大小刚好为4,也就说结构体中的数组没有占用内存。为什么会没有占用内存,我们平时用数组时不时都要明确指明数组大小的吗?但这里却可以编译通过呢?这就是我们常说的动态数组,也就是柔性数组。
先不要乱,让我们再看一段代码
#include <stdio.h> #include <malloc.h> typedef struct _SoftArray { int len; int array[]; } SoftArray; int main() { int len = 10; SoftArray *p = (SoftArray *)malloc(sizeof(SoftArray) + sizeof(int) * len); printf("After the malloc function the struct's size is %d\n",sizeof(SoftArray)); return 0; }
运行结果:
是不是有点奇怪,为什么申请了内存后结构体大小还是4呢?
原因是:动态申请的内存只是申请给数组拓展所用,从上个程序我们可以看出结构体的大小在创建时已经确定了,array明确来说不算是结构体成员,只是挂羊头卖狗肉而已。
下面我们来看看关于柔性数组的资料:
1、什么是柔性数组?
柔性数组既数组大小待定的数组, C语言中结构体的最后一个元素可以是大小未知的数组,也就是所谓的0长度,所以我们可以用结构体来创建柔性数组。
2、柔性数组有什么用途 ?
它的主要用途是为了满足需要变长度的结构体,为了解决使用数组时内存的冗余和数组的越界问题。
3、用法:在一个结构体的最后 ,申明一个长度为空的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名
本身不占空间,它只是一个偏移量, 数组名这个符号本身代 表了一个不可修改的地址常量 (注意:数组名永远都不会是指针! ),但对于这个数组的大小,我们可以进行动态分配,对于编译器而言,数组名仅仅是一个符号,它不会占用任何空间,它在结构体中,只是代表了一个偏移量,代表一个不可修改的地址常量!
对于柔性数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等:
typedef struct _SoftArray { Int len; int array[]; }SoftArray;
柔性数组用途
这样的变长数组常用于网络通信中构造不定长数据包,不会浪费空间浪费网络流量,比如我要发送1024字节的数据,如果用定长包,假设定长包的长度为2048,就会浪费1024个字节的空间,也会造成不必要的流量浪费。
其实柔性数组成员在实现跳跃表时有它特别的用法,在Redis的SDS数据结构中和跳跃表的实现上,也使用柔性数组成员。