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, %s\n", 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, %s\n", 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, %s\n", softbuffer->len, softbuffer->data);
}

释放内存:

free(softbuffer);
softbuffer = NULL;

对比使用指针和柔性数组会发现,使用柔性数组的优点:

  • 由于结构体使用指针地址不连续(两次 malloc),柔性数组地址连续,只需要一次 malloc,同样释放前者需要两次,后者可以一起释放。

  • 在数据拷贝时,结构体使用指针时,必须拷贝它指向的内存,内存不连续会存在问题,柔性数组可以直接拷贝。

  • 减少内存碎片,由于结构体的柔性数组和结构体成员的地址是连续的,即可一同申请内存,因此更大程度地避免了内存碎片。另外由于该成员本身不占结构体空间,因此,整体而言,比普通的数组成员占用空间要会稍微小点。

缺点:对结构体格式有要求,必要放在最后,不是唯一成员。

3 总结

在日常编程中,有时需要在结构体中存放一个长度是动态的字符串(也可能是其他数据类型),可以使用柔性数组,柔性数组是一种能够巧妙地解决数组内存的冗余和数组的越界问题一种方法。非常值得大家学习和借鉴。

推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

听说有人不了解柔性数组相关推荐

  1. 浅谈C语言动态内存分配及柔性数组

    文章目录 前言 1.动态内存的简单介绍 1.动态内存分配是什么? 2.为什么存在动态内存分配? 3.动态内存分配具体方法 1.动态内存函数 2.动态内存注意事项 2.经典面试题分析 3.C/C++程序 ...

  2. 结构体中最后一个成员为[0]或[1]长度数组(柔性数组成员)的用法

    结构体中最后一个成员为[0]长度数组的用法:这是个广泛使用的常见技巧,常用来构成缓冲区.比起指针,用空数组有这样的优势:(1).不需要初始化,数组名直接就是所在的偏移:(2).不占任何空间,指针需要占 ...

  3. 动态内存分配与柔性数组

    什么时动态内存分配 一般我们写程序都是在栈区分配空间,如果我们想根据需求想随时存放随时释放数据,堆区可以实现根据需求想系统申请所需大小的空间. 建立内存的动态分配 内存的动态分配是通过系统提供的函数来 ...

  4. C/C++ 中的0长数组(柔性数组)

    C/C++ 中的0长数组(柔性数组) 在标准C和C++中0长数组如charArray[0]是不允许使用的,因为这从语义逻辑上看,是完全没有意义的. 但是,GUN中却允许使用,而且,很多时候,应用在了变 ...

  5. c99变长数组_你学过数组,那你知道柔性数组吗?

    1 引言 定长数组包 在平时的开发中,缓冲区数据收发时,如果采用缓冲区定长包,假定大小是 1k,MAX_LENGTH 为 1024.结构体如下: // 定长缓冲区struct max_buffer{ ...

  6. 写给过去的自己-No.2-数据结构篇-初尝柔性数组

    过去你的自己,你好.     照顾宝宝,写完第一篇就没什么时间,既然上次讲的就是数据结构,这次也讲点相关的.     其实接触柔性数组也是个比较奇妙的过程,你以后会遇到个学长,毕业后从事软件行业,在中 ...

  7. 3----结构体中使用柔性数组

    结构体中最后一个成员为[0]长度数组的用法:这是个广泛使用的常见技巧,常用来构成缓冲区.比起指针,用空数组有这样的优势:(1).不需要初始化,数组名直接就是所在的偏移:(2).不占任何空间,指针需要占 ...

  8. 2-结构体的最后一个成员的定义-C语言中的柔性数组-

    深入浅出C语言中的柔性数组 在日常的编程中,有时候需要在结构体中存放一个长度动态的字符串,一般的做法,是在结构体中定义一个指针成员,这个指针成员指向该字符串所在的动态内存空间,例如: [cpp] vi ...

  9. c判断char数组是否为空_你学过数组,那你知道柔性数组吗?

    1 引言 定长数组包 在平时的开发中,缓冲区数据收发时,如果采用缓冲区定长包,假定大小是 1k,MAX_LENGTH 为 1024.结构体如下: // 定长缓冲区struct max_buffer{ ...

最新文章

  1. python 属性可修改的装饰器 监控函数的运行时间
  2. Oracle安装配置手册
  3. (格式化字符串漏洞).fini.array劫持,使程序流程循环进行
  4. iPad+MacBook+安卓手机的图书馆工作方案!超高效率!堪比移动工作站!
  5. 幅度和幅值有区别吗_童溢金:白银期货和现货白银的区别在哪,你知道吗?
  6. JDBC实例--JDBC连接池技术解密,连接池对我们不再陌生
  7. mysql主主同步修复
  8. 有些新手小白创业者开奶茶店,为什么总是容易上当受骗呢?
  9. java robots协议检测工具
  10. 2021-05-11
  11. html如何制作表格合并,html如何合并表格
  12. 用python画人物素描_python实现图片素描效果
  13. Python 计时器倒计时弹窗提醒
  14. 爱你,我永远的 Elaine
  15. 线性代数笔记12——列空间和零空间
  16. Qt 使用-自定义菜单栏
  17. CRM:网站证书过期怎么办
  18. Towards Adversarial Attack on Vision-Language Pre-training Models
  19. python 列表删除元素
  20. 2022/03/21hackthebox取证emo

热门文章

  1. 快速开发系统之前台页面 ---- NO1----登录页面
  2. 2013腾讯编程马拉松初赛(3月20日)
  3. Spring全家桶面试真题
  4. 计算机二级access选择题技巧,计算机二级access考试注意事项及解题技巧策略
  5. python人脸识别代码百度ai_python百度AI人脸识别API测试
  6. 编程随想 关系图_IT什么岗位比较好找工作?一张金字塔图就能明白
  7. eigen库安装_OpenCV+Eigen上位机程序移植(七十一)
  8. HDFS的简介及基本操作(常用的命令参数介绍)
  9. c语言结构体指针初始化
  10. LeetCode371——Sum of Two Integers(不用+)