来源:公众号【鱼鹰谈单片机】
作者:鱼鹰Osprey
ID :emOsprey
动态数组???
不知道你是否听说过 C99 有一个动态数组的特性,也就是说,数组大小可以根据需要动态的变化。
我们都知道,在 C89 模式下,数组的声明只能是这样:
但到了 C99,数组的大小可以用变量代替,根据需要变化:
有些人为了尝鲜或者为了使用方便,可能会在程序中写上类似的代码。
一般情况下,代码运行很正常,没有一点问题。
但在运行时间需要严格控制的情况下,这段代码就不讲武(码)德了。
防出去了昂
我们都知道,嵌入式系统的一大好处就是运行时间可控,是实时的系统,所以它可以做一些高实时性的工作,比如控制、采样等。
就拿采样来说,一般都会要求采样率,比如 100Hz、10Hz,换算到时间单位,就是需要 10 毫秒、100毫秒读取一次数据,这个数据可能是内部寄存器(比如ADC),也可能是外部的器件通过 I2C、SPI等总线获取,而一般来说,这些总线的通信时间是稳定的、可控的。
但是有一天,你发现你的 SPI 驱动程序运行时间变得不再可控,它有时50us 完成一次数据的采集,有时需要 1 ms才完成,总之没有规律可循,唯一的规律就是,当系统全面开始工作时,这个时间大部分在 1 ms 以上,只有很少几次是几十微秒就完成了执行。
在 10 Hz 采样率下,1 ms 误差也不算太大,但当在 100 Hz采样时,时间误差就是 10%,不可忽略
你仔仔细细的查看了实现代码,发现就是简单的 SPI 通信,基本上都是判断、赋值操作,还有就是使用循环等待标志位(使用硬件 SPI),。
(或许你会怀疑循环等等标志代码导致了时间的不确定,但我的第一直觉告诉我不是它,因为 SPI 的通信时间是可控的(只要器件正常,从机一定会返回数据),STM32F1 系列的硬件 SPI 通信鱼鹰也用了五六年,不应该有问题才对)
这些我全部防出去了昂(甚至鱼鹰都考虑到线程执行可能受到中断的影响,特地在问题代码执行期间禁止了中断)。
没办法,我只能停停,放下源码本身的分析,拿出了杀手锏:《KEIL 下如何准确测量代码执行时间?》开始对问题代码进行时间测量。
有备而来
经过几番测量,很快昂,定位到类似下面的代码:
发现竟然在52到57行之间花费了大量时间。就一些局部变量的定义,唯一和传统写法不同的是使用了动态数组,怎么会花费这么多时间?
按照传统写法,这里应该使用固定大小的数组。
我大意了啊,没有闪,当时移植这份代码的时候就留意到了这个另类写法,当时还特地看了一下实现,但最终还是栽在了这里。
这段代码是乱打(写)的吗?他可不是乱打(写)的,格式清晰、移植方便、还有各种异常处理,明显有备而来。
来、骗,来、偷袭我这经验丰富的老同志。
这好吗?这不好,我劝他耗子尾汁。
寻根问底
事实上,如果对时间要求不是很高的话,这段代码不会有任何问题,它的基本读取功能是没有任何问题的,只是说它的执行时间很不稳定,有的时候几十微秒就可以执行完毕,有时候可能需要几毫秒时间,还有极端的可能是直接死机(Hardfault)!
那么动态数组是如何实现的,或者说它的本质是什么呢?
本质就是使用 malloc 函数申请堆空间(在 rt-thread 中又会调用 rt_malloc),然后在离开函数前使用 free 函数释放堆空间。
可以查看汇编确认:
看到这里,你也就知道为什么会出现之前的现象了吧。
系统未完全运行前,很少有线程申请堆空间,所以执行时间比较稳定,因为它能快速的找到合适的内存块,一旦系统里所有线程正式工作了,涉及到大量的内存申请与释放,有大量的内存碎片,也就不容易找到合适的内存,这样执行时间也就不稳定了,这对于实时要求高的功能是一个灾难。
所以,如果你的功能不要求实时性的话,使用动态数组是可以的,一旦你的功能要求实时性,那么使用静态数组才是更好的选择(如果使用静态数组,一定要注意使用范围,最好加上断言机制),如果代码是在中断执行,rt-thread系统中,则必须使用静态数组,否则 rt_malloc 无法正常执行(断言失败)。
你防住了吗?
推荐阅读:
-THE END-
如果对你有帮助,记得转发分享哦
微信公众号「鱼鹰谈单片机」
每周一更单片机知识
长按后前往图中包含的公众号关注
转载:https://blog.csdn.net/weixin_42876465/article/details/109975377