柔性数组
1 定长数组
在日常编码中时常需要缓存接收到的数据,通常不知道数据具体的长度,此时需要预先分配一个足够大的数组。下面例子中,就是我们申请的1024个字节长度的数组。
1.1 定义结构
#define MAX_BUFF_LEN 1024
struct solid_array{
unsigned int len;
char buff[MAX_BUFF_LEN];
};
1.2 申请内存
struct solid_array * pBuf = (struct solid_array *) malloc( sizeof(unsigned int) + sizeof(char) * MAX_BUFF_LEN);
if(pBuf){
pBuf->len = CUR_BUFF_LEN;
memset(pBuf->buff, 0, MAX_BUFF_LEN);
memcpy(pBuf->buff, "solid array test!", CUR_BUFF_LEN);
}
1.3 释放内存
free(pBuf);
pBuf = NULL;
1.4 优缺点
缺点:
- 如果接收到大量的数据包,数据包的大小都不超过24字节,那么每一个数据包都将有1000个字节的内存空间浪费,而内存又是相当昂贵的资源。
- 如果是大量发送此数据结构的数据包,也会对流量造成极大浪费
优点:
- 内存地址连续内存碎片化少,只需要一次释放内存。
2 指针数据
为了解决定长数组内存浪费,通常声明一个指针,根据接收的数据包大小,动态申请内存。
2.1 定义结构
struct dynamic_array{
unsigned int len;
char* buff;
};
2.2 申请内存
struct dynamic_array* pBuf = (struct dynamic_array*) malloc( sizeof(unsigned int) + sizeof(char *));
if(pBuf){
pBuf->len = CUR_BUFF_LEN;
pBuf->buff = (char *) malloc(sizeof(char ) * CUR_BUFF_LEN);
if(pBuf->buff){
memset(pBuf->buff, 0, CUR_BUFF_LEN);
memcpy(pBuf->buff, "dynamic array test!", CUR_BUFF_LEN);
}
}
2.3 释放内存
free(pBuf->buff);
pBuf->buff = NULL;
free(pBuf);
pBuf = NULL;
2.4 优缺点
缺点:
- 2次申请内存,效率低下,内存碎片化
- 作为API参数对外提供数据,容易遗漏pBuf->buff释放步骤,导致内存泄露
- 数据拷贝时,必须拷贝它指向的内存,不能直接赋值。
优点:
- 节省内存空间
3 柔性数组
柔性数组成员(flexible array member)也叫伸缩性数组成员,这种代码结构产生于对动态结构体的需求。在日常的编程中,有时候需要在结构体中存放一个长度动态的字符串,鉴于这种代码结构所产生的重要作用,C99 甚至把它收入了标准中:
柔性数组是 C99 标准引入的特性,所以当你的编译器提示不支持的语法时,请检查你是否开启了 C99 选项或更高的版本支持。
3.1 定义结构
struct flexible_array{
unsigned int len;
char buff[];
};
3.2 结构约束
1、柔性数组成员必须定义在结构体里面且为最后元素;
2、结构体中不能单独只有柔性数组成员;
3、柔性数组不占内存。
在一个结构体的最后,申明一个长度为空的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为 0 的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量,数组名这个符号本身代表了一个不可修改的地址常量,
但对于这个数组的大小,我们可以进行动态分配,对于编译器而言,数组名仅仅是一个符号,它不会占用任何空间,它在结构体中,只是代表了一个偏移量,代表一个不可修改的地址常量!
3.3 申请内存
struct flexible_array* pBuf = (struct flexible_array*) malloc( sizeof(unsigned int) + sizeof(char ) * CUR_BUFF_LEN);
if(pBuf){
pBuf->len = CUR_BUFF_LEN;
memset(pBuf->buff, 0, CUR_BUFF_LEN);
memcpy(pBuf->buff, "flexible array test!", CUR_BUFF_LEN);
}
3.4 释放内存
free(pBuf);
pBuf = NULL;
3.5 优缺点
优点:
- 由于结构体使用指针地址不连续(两次 malloc),柔性数组地址连续,只需要一次 malloc,同样释放前者需要两次,后者可以一起释放。
- 在数据拷贝时,结构体使用指针时,必须拷贝它指向的内存,内存不连续会存在问题,柔性数组可以直接拷贝。
- 减少内存碎片,由于结构体的柔性数组和结构体成员的地址是连续的,即可一同申请内存,因此更大程度地避免了内存碎片。另外由于该成员本身不占结构体空间,因此,整体而言,比普通的数组成员占用空间要会稍微小点。
缺点:
- 对结构体格式有要求,必要放在最后,不是唯一成员。
3.6 应用场景
Redis的简单动态数组(SDS)结构
struct sdshdr {
// buf 中已占用空间的长度
int len;
// buf 中剩余可用空间的长度
int free;
// 字节数组
char buf[];
}
SDS优点:
- 常数复杂度获取字符串长度:O(1)
- 杜绝缓冲区溢出
- 减少修改字符串时带来的内存重分配次数
- 二进制安全