|
1 引言 定长数组包 在平时的开发中,缓冲区数据收发时,如果采用缓冲区定长包,假定大小是 1k,MAX_LENGTH 为 1024。结构体如下: // 定长缓冲区0 ~. N$ y8 U$ g$ s9 j' _" s! vstruct max_buffer" }0 q" N# }! B { int len;$ Q2 W. a" c7 |" {2 l( s8 z char data[MAX_LENGTH];( O% V S1 l( i- c# S8 y }; 数据结构的大小 >= sizeof(int) + sizeof(char) * MAX_LENGTH为了防止数据溢出的情况,data 的长度一般会设置得足够大,但也正是因为这样,才会导致数组的冗余。 假如发送 512 字节的数据, 就会浪费 512 个字节的空间, 平时通信时,大多数是心跳包,大小远远小于 1024,除了浪费空间还消耗很多流量。 内存申请: if ((m_buffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL), v% z, [( D' u% w6 w8 h7 r+ P{" d* u5 [% c8 ~5 r+ O9 b2 u% J, h m_buffer->len = CUR_LENGTH;/ C6 a& Q. f+ L' N5 Z7 u/ B; H memcpy(m_buffer->data, "max_buffer test", CUR_LENGTH); printf("%d, %s\n", m_buffer->len, m_buffer->data); } 内存释放: free(m_buffer);m_buffer = NULL; v9 X7 p1 `8 r7 c: ^6 ~ 指针数据包 为了避免空间上的浪费,我们可以将上面的长度为 MAX_LENGTH 的定长数组换为指针, 每次使用时动态的开辟 CUR_LENGTH 大小的空间。数据包结构体定义: struct point_buffer{ int len;6 a$ o, C$ e2 |2 c' c2 ? g% V( r char *data; };) x8 o% b4 {1 W: b 数据结构大小 >= sizeof(int) + sizeof(char *)但在内存分配时,需要两步进行:
内存申请: if ((p_buffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)' R1 c `3 e2 p" }4 Y! T{% @3 R& d8 z) d9 k" k p_buffer->len = CUR_LENGTH; if ((p_buffer->data = (char *)malloc(sizeof(char) * CUR_LENGTH)) != NULL)5 J: L) U' v5 V8 I# w ]3 Y {6 ~4 k V; E/ Q$ T memcpy(p_buffer->data, "point_buffer test", CUR_LENGTH);# q) ^: W; u/ K6 q! F" L printf("%d, %s\n", p_buffer->len, p_buffer->data); }. V) a. ]! D4 t } 内存释放: free(p_buffer->data);free(p_buffer);9 A9 K+ | x! C: x3 f, ~- h; g; \ p_buffer = NULL;2 I7 P4 {7 a; y4 d+ h 虽然这样能够节约内存,但是两次分配的内存是不连续的, 需要分别对其进行管理,导致的问题就是需要对结构体和数据分别申请和释放内存,这样对于程序员来说无疑是一个灾难,因为这样很容易导致遗忘释放内存造成内存泄露。 有没有更好的方法呢?那就是今天的主题柔性数组。 2 柔性数组0 m: y# g$ _" D i% c什么是柔性数组? 柔性数组成员(flexible array member)也叫伸缩性数组成员,这种代码结构产生于对动态结构体的需求。在日常的编程中,有时候需要在结构体中存放一个长度动态的字符串,鉴于这种代码结构所产生的重要作用,C99 甚至把它收入了标准中:
柔性数组是 C99 标准引入的特性,所以当你的编译器提示不支持的语法时,请检查你是否开启了 C99 选项或更高的版本支持。 C99 标准的定义如下: struct test {" @; L$ ?7 e! ?0 W5 ashort len; // 必须至少有一个其它成员 char arr[]; // 柔性数组必须是结构体最后一个成员(也可是其它类型,如:int、double、...)# ]4 F$ o: i. [ };% H$ }- ?' W2 r2 v
在一个结构体的最后,申明一个长度为空的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为 0 的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量,数组名这个符号本身代表了一个不可修改的地址常量, 但对于这个数组的大小,我们可以进行动态分配,对于编译器而言,数组名仅仅是一个符号,它不会占用任何空间,它在结构体中,只是代表了一个偏移量,代表一个不可修改的地址常量! 对于柔性数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等, 其实柔性数组成员在实现跳跃表时有它特别的用法,在Redis的SDS数据结构中和跳跃表的实现上,也使用柔性数组成员。它的主要用途是为了满足需要变长度的结构体,为了解决使用数组时内存的冗余和数组的越界问题。 柔性数组解决引言的例子/ r' \& e2 y" O6 h2 G; X p//柔性数组* ?; v% P3 o! Y$ }9 s# m struct soft_buffer {% o) z7 T5 Z9 ^9 E int len;# S2 j6 f* k2 h. v7 @0 W9 q char data[0]; };4 M: V3 q8 a2 s 数据结构大小 = 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;6 ~0 S3 P* C. [8 t) H memcpy(softbuffer->data, "softbuffer test", CUR_LENGTH);( V; Z" V9 F" O printf("%d, %s\n", softbuffer->len, softbuffer->data);- ~7 L+ p* N; K } 释放内存: free(softbuffer);5 M9 t' H$ ~3 P9 [+ L& ^; fsoftbuffer = NULL;& n N7 d3 a1 W" g 对比使用指针和柔性数组会发现,使用柔性数组的优点:
缺点:对结构体格式有要求,必要放在最后,不是唯一成员。 3 总结在日常编程中,有时需要在结构体中存放一个长度是动态的字符串(也可能是其他数据类型),可以使用柔性数组,柔性数组是一种能够巧妙地解决数组内存的冗余和数组的越界问题一种方法。非常值得大家学习和借鉴。 5 v9 ~ V& ^5 y8 H8 C( x- \. q |
| 柔性数组 |
微信公众号
手机版