新闻  |   论坛  |   博客  |   在线研讨会
这段代码不讲武德,劝你耗子尾汁
鱼鹰谈单片机 | 2021-01-06 11:54:52    阅读:449   发布文章

动态数组???

不知道你是否听说过 C99 有一个动态数组的特性,也就是说,数组大小可以根据需要动态的变化。

我们都知道,在 C89 模式下,数组的声明只能是这样:

4.png

但到了 C99,数组的大小可以用变量代替,根据需要变化:

3.png

有些人为了尝鲜或者为了使用方便,可能会在程序中写上类似的代码。

一般情况下,代码运行很正常,没有一点问题。

但在运行时间需要严格控制的情况下,这段代码就不讲武(码)德了。

防出去了昂

我们都知道,嵌入式系统的一大好处就是运行时间可控,是实时的系统,所以它可以做一些高实时性的工作,比如控制、采样等。

就拿采样来说,一般都会要求采样率,比如 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 下如何准确测量代码执行时间?》开始对问题代码进行时间测量。

有备而来

经过几番测量,很快昂,定位到类似下面的代码:

2.png

发现竟然在52到57行之间花费了大量时间。就一些局部变量的定义,唯一和传统写法不同的是使用了动态数组,怎么会花费这么多时间?

按照传统写法,这里应该使用固定大小的数组。

我大意了啊,没有闪,当时移植这份代码的时候就留意到了这个另类写法,当时还特地看了一下实现,但最终还是栽在了这里。                                                   

这段代码是乱打(写)的吗?他可不是乱打(写)的,格式清晰、移植方便、还有各种异常处理,明显有备而来。

来、骗,来、偷袭我这经验丰富的老同志。

这好吗?这不好,我劝他耗子尾汁。

寻根问底

事实上,如果对时间要求不是很高的话,这段代码不会有任何问题,它的基本读取功能是没有任何问题的,只是说它的执行时间很不稳定,有的时候几十微秒就可以执行完毕,有时候可能需要几毫秒时间,还有极端的可能是直接死机(Hardfault)!

那么动态数组是如何实现的,或者说它的本质是什么呢?

本质就是使用 malloc 函数申请堆空间(在 rt-thread 中又会调用 rt_malloc),然后在离开函数前使用 free 函数释放堆空间。

可以查看汇编确认:

1.png5.png

看到这里,你也就知道为什么会出现之前的现象了吧。

系统未完全运行前,很少有线程申请堆空间,所以执行时间比较稳定,因为它能快速的找到合适的内存块,一旦系统里所有线程正式工作了,涉及到大量的内存申请与释放,有大量的内存碎片,也就不容易找到合适的内存,这样执行时间也就不稳定了,这对于实时要求高的功能是一个灾难。

所以,如果你的功能不要求实时性的话,使用动态数组是可以的,一旦你的功能要求实时性,那么使用静态数组才是更好的选择(如果使用静态数组,一定要注意使用范围,最好加上断言机制),如果代码是在中断执行,rt-thread系统中,则必须使用静态数组,否则 rt_malloc 无法正常执行(断言失败)。

你防住了吗?

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
推荐文章
最近访客