共计 2919 个字符,预计需要花费 8 分钟才能阅读完成。
1 引言
定长数组包
在平时的开发中,缓冲区数据收发时,如果采纳缓冲区定长包,假设大小是 1k,MAX_LENGTH
为 1024。构造体如下:
// 定长缓冲区
struct max_buffer
{
int len;
char data[MAX_LENGTH];
};
数据结构的大小 >= sizeof(int)
+ sizeof(char) * MAX_LENGTH
为了避免数据溢出的状况,data 的长度个别会设置得足够大,但也正是因为这样,才会导致数组的冗余。
如果发送 512 字节的数据, 就会节约 512 个字节的空间, 平时通信时,大多数是心跳包,大小远远小于 1024,除了节约空间还耗费很多流量。
内存申请:
if ((m_buffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
{
m_buffer->len = CUR_LENGTH;
memcpy(m_buffer->data, "max_buffer test", CUR_LENGTH);
printf("%d, %sn", m_buffer->len, m_buffer->data);
}
内存开释:
free(m_buffer);
m_buffer = NULL;
指针数据包
为了防止空间上的节约,咱们能够将下面的长度为 MAX_LENGTH
的定长数组换为指针, 每次应用时动静的开拓 CUR_LENGTH
大小的空间。数据包构造体定义:
struct point_buffer
{
int len;
char *data;
};
数据结构大小 >= sizeof(int)
+ sizeof(char *)
但在内存调配时,须要两步进行:
- 需为构造体调配一块内存空间;
- 为构造体中的成员变量分配内存空间;
内存申请:
if ((p_buffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
{
p_buffer->len = CUR_LENGTH;
if ((p_buffer->data = (char *)malloc(sizeof(char) * CUR_LENGTH)) != NULL)
{memcpy(p_buffer->data, "point_buffer test", CUR_LENGTH);
printf("%d, %sn", p_buffer->len, p_buffer->data);
}
}
内存开释:
free(p_buffer->data);
free(p_buffer);
p_buffer = NULL;
尽管这样可能节约内存,然而两次调配的内存是不间断的, 须要别离对其进行治理,导致的问题就是须要对构造体和数据别离申请和开释内存,这样对于程序员来说无疑是一个劫难,因为这样很容易导致忘记开释内存造成内存泄露。
有没有更好的办法呢?那就是明天的主题柔性数组。
2 柔性数组
什么是柔性数组?
柔性数组成员(flexible array member)也叫伸缩性数组成员,这种代码构造产生于对动静构造体的需要。在日常的编程中,有时候须要在构造体中寄存一个长度动静的字符串,鉴于这种代码构造所产生的重要作用,C99 甚至把它支出了规范中:
As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member.
柔性数组是 C99 规范引入的个性,所以当你的编译器提醒不反对的语法时,请查看你是否开启了 C99 选项或更高的版本反对。
C99 规范的定义如下:
struct test {
short len; // 必须至多有一个其它成员
char arr[]; // 柔性数组必须是构造体最初一个成员(也可是其它类型,如:int、double、...)};
- 柔性数组成员必须定义在构造体外面且为最初元素;
- 构造体中不能独自只有柔性数组成员;
- 柔性数组不占内存。
在一个构造体的最初,申明一个长度为空的数组,就能够使得这个构造体是可变长的。对于编译器来说,此时长度为 0 的数组并不占用空间,因为数组名自身不占空间,它只是一个偏移量,数组名这个符号自身代表了一个不可批改的地址常量,
但对于这个数组的大小,咱们能够进行动态分配, 对于编译器而言,数组名仅仅是一个符号,它不会占用任何空间,它在构造体中,只是代表了一个偏移量,代表一个不可批改的地址常量!
对于柔性数组的这个特点,很容易结构出变成构造体,如缓冲区,数据包等等,其实柔性数组成员在实现跳跃表时有它特地的用法,在 Redis 的 SDS 数据结构中和跳跃表的实现上,也应用柔性数组成员。它的主要用途是为了 满足需要变长度的构造体,为了解决应用数组时内存的冗余和数组的越界问题
。
柔性数组解决引言的例子
// 柔性数组
struct soft_buffer
{
int len;
char data[0];
};
数据结构大小 = sizeof(struct soft_buffer)
= sizeof(int)
,这样的变长数组罕用于网络通信中结构不定长数据包, 不会节约空间节约网络流量。
申请内存:
if ((softbuffer = (struct soft_buffer *)malloc(sizeof(struct soft_buffer) + sizeof(char) * CUR_LENGTH)) != NULL)
{
softbuffer->len = CUR_LENGTH;
memcpy(softbuffer->data, "softbuffer test", CUR_LENGTH);
printf("%d, %sn", softbuffer->len, softbuffer->data);
}
开释内存:
free(softbuffer);
softbuffer = NULL;
比照应用指针和柔性数组会发现,应用柔性数组的长处:
- 因为构造体应用指针地址不间断(两次 malloc),柔性数组地址间断,只须要一次 malloc,同样开释前者须要两次,后者能够一起开释。
- 在数据拷贝时,构造体应用指针时,必须拷贝它指向的内存,内存不间断会存在问题,柔性数组能够间接拷贝。
- 缩小内存碎片,因为构造体的柔性数组和构造体成员的地址是间断的,即可一起申请内存,因而更大程度地防止了内存碎片。另外因为该成员自身不占构造体空间,因而,整体而言,比一般的数组成员占用空间要会略微小点。
毛病:对构造体格式有要求,必要放在最初,不是惟一成员。
3 总结
在日常编程中,有时须要在构造体中寄存一个长度是动静的字符串(也可能是其余数据类型),能够应用柔性数组,柔性数组是一种可能奇妙地解决数组内存的冗余和数组的越界问题一种办法。十分值得大家学习和借鉴。