听说有人不了解柔性数组

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语言

我的知识小密圈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/466639.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Transformer模型拆解分析

资源来自:DataWhale 学习资料 最近看了DataWhale 的Transformer图解,突然对Transformer的结构图有了更加清晰的理解,特此记录。 1、大框架 Transformer是由6个encoder和6个decoder组成,模型的具体实现是model变量里边&#xff0…

设计模式学习笔记六:.NET反射工厂

1. 简述 通过前面的学习,我们以传统的方式实现了简单工厂,工厂方法和抽象工厂,但是有些场合下如此处理,代码会变得冗余并且难以维护。假设我们要创建交通工具。可以是汽车,火车,轮船等&#xff…

在unity 中,使用http请求,下载文件到可读可写路径

在这里我用了一个线程池,线程池参数接收一个带有object参数的,无返回值的委托 ,下载用到的核心代码,网上拷贝的,他的核心就是发起一个web请求,然后得到请求的响应,读取响应的流 剩下的都是常见的…

在tinyalsa上抓取音频

我们经常会遇到这样的问题,应用读取到的音频有问题,需要在tinyalsa里面读取音频来确认是底层音频有问题,还是应用处理之后存在的问题。所以,这个patch就出现了代码的逻辑很简单,主要是在pcm_read的时候,同时…

STM32——GPIO(1)

STM32——GPIO 宗旨:技术的学习是有限的,分享的精神是无限的。 【对单片机的操作就是控制IO口】 一、GPIO(通用输入输出口) 1、选定需要的引脚(对应哪一个IO口); 2、配置需要的功能&#xf…

【opencv学习笔记八】创建TrackBar轨迹条

createTrackbar这个函数我们以后会经常用到,它创建一个可以调整数值的轨迹条,并将轨迹条附加到指定的窗口上,使用起来很方便。首先大家要记住,它往往会和一个回调函数配合起来使用。先看下他的函数原型: int createTra…

父母悄悄给自己买房,我很生气,要怎么调整心态?

——问题我是独生子,今年满24岁刚上研一(普通211)。家庭四川小城市,情况一般,父母二人体制内月薪总计一万元以内,家里积蓄20W-30W,公积金情况不清楚。从小母子关系比较僵硬,母亲小学…

语音处理入门——语音的声学处理

语音的声学处理通常称为特征提取或者信号分析,特征是表示语音信号的一个时间片的矢量。常见的特征类型有LPC(线性预测编码)特征和PLP(感知线性预测编码),该特征称为声谱特征,使用形成波形的不同…

基础呀基础,用二极管防止反接,你学会了吗?

使用新的电源,第一次给设备供电时,要特别注意电源的正负极性标注。比如电源适配器,铭牌上面有标注插头的极性。这个符号说明插头的里面是正极,外面是负极,即“内正外负”。但是也有反过来的,下面这款是“内…

李宏毅的可解释模型——三个任务

1、问题 观看了李宏毅老师的机器学习进化课程之可解释的机器学习,课程中对主要是针对黑盒模型进行白盒模型转化的技巧和方法进行了简单介绍,详细细节可以参考《Interpretable Machine Learning》。像一些线性模型、树形模型是可解释的ML model&#xff…

柔性数组和环形队列之间的故事

之前的文章,讲解了柔性数组,有很多人留言,提到一些问题。刚好,之前发关于环形队列的文章有些问题,这次刚好拿出来一起说一下,并用柔性数组实现一个环形队列。柔性数组的上一篇文章环形队列C语言实现文章1、…

STM32——时钟系统

STM32——时钟系统 宗旨:技术的学习是有限的,分享的精神是无限的。 一、时钟树 普通的MCU,一般只要配置好GPIO 的寄存器,就可以使用了。STM32为了实现低功耗,设计了非常复杂的时钟系统,必须开启外设时钟才…

目标检测发展路程(一)——Two stage

目标检测是计算机视觉领域中非常重要的一个研究方向,它是将图像或者视频中目标与其他不感兴趣的部分进行区分,判断是否存在目标,确定目标位置,识别目标种类的任务,即定位分类。传统的目标检测模型有VJ.Det[1,2],HOG.De…

都2021年了,c/c++开发竟然还能继续吃香??

年后就迎来了金三银四,你准备好2021年的跳槽涨薪计划了吗?今天我就来给大家分享,c/c作为老牌开发常青树,还能与java/python/go较较劲的岗位和技术在哪里!同时,给大家整理了2021年系统全面技术学习资料。文末…

目标检测模型——One stage(YOLO v5的模型解析及应用)

1. 简介 目标检测分为Two stage和One stage,只使用一个网络同时产生候选区域并预测出物体的类别和位置,所以它们通常被叫做单阶段检测算法(One stage)。本篇文章只讲One stage模型,常见的模型有YOLO,SSD。 目标检测发…

腾讯回应QQ读取用户浏览器历史记录

腾讯QQ官方认证账号在知乎回应“QQ扫描读取所有浏览器的历史记录”表示,PC QQ存在读取浏览器历史用以判断用户登录安全风险的情况,读取的数据用于在PC QQ的本地客户端中判断是否恶意登录。所有相关数据不会上传至云端,不会储存,也…

OCR系列——总体概述

最近参加了百度Paddle的动手学OCR课程,特此做一个学习总结。 1. 简介 OCR(Optical Character Recognition,光学字符识别)是计算机视觉重要方向,传统的OCR一般面向扫描文档类对象,现在的OCR是指场景文字识…

STM32——系统滴答定时器

STM32——系统滴答定时器 宗旨:技术的学习是有限的,分享的精神是无限的。 一、SysTick【内核中】 【风格:先描述一下库对寄存器的封装,再举例实现某些功能】 SysTick定时器被捆绑在NVIC中,用于产生SysTick异常&#…

你会用while(1)还是for(;;)写循环代码?

看代码看到for(;;)&#xff0c;然后觉得为什么不写成while(1)呢&#xff0c;所以就做了下面的测试。网上有解释&#xff0c;因为while需要做一次判断&#xff0c;理论上执行会花费的时间更久&#xff0c;for(;;)只是执行了两次空语句&#xff0c;执行会更快for.c#include <s…

OCR系列——文本检测任务

1. 简介 文本检测任务是找出图像或视频中的文字位置。不同于目标检测任务&#xff0c;目标检测不仅要解决定位问题&#xff0c;还要解决目标分类问题。 目标检测和文本检测同属于“定位”问题。但是文本检测无需对目标分类&#xff0c;并且文本形状复杂多样。 当前所说的文本…