這段代碼不講武德,勸你耗子尾汁
動態(tài)數組???
不知道你是否聽說過 C99 有一個動態(tài)數組的特性,也就是說,數組大小可以根據需要動態(tài)的變化。
我們都知道,在 C89 模式下,數組的聲明只能是這樣:

但到了 C99,數組的大小可以用變量代替,根據需要變化:

有些人為了嘗鮮或者為了使用方便,可能會在程序中寫上類似的代碼。
一般情況下,代碼運行很正常,沒有一點問題。
但在運行時間需要嚴格控制的情況下,這段代碼就不講武(碼)德了。
防出去了昂
我們都知道,嵌入式系統(tǒng)的一大好處就是運行時間可控,是實時的系統(tǒng),所以它可以做一些高實時性的工作,比如控制、采樣等。
就拿采樣來說,一般都會要求采樣率,比如 100Hz、10Hz,換算到時間單位,就是需要 10 毫秒、100毫秒讀取一次數據,這個數據可能是內部寄存器(比如ADC),也可能是外部的器件通過 I2C、SPI等總線獲取,而一般來說,這些總線的通信時間是穩(wěn)定的、可控的。
但是有一天,你發(fā)現你的 SPI 驅動程序運行時間變得不再可控,它有時50us 完成一次數據的采集,有時需要 1 ms才完成,總之沒有規(guī)律可循,唯一的規(guī)律就是,當系統(tǒng)全面開始工作時,這個時間大部分在 1 ms 以上,只有很少幾次是幾十微秒就完成了執(zhí)行。
在 10 Hz 采樣率下,1 ms 誤差也不算太大,但當在 100 Hz采樣時,時間誤差就是 10%,不可忽略
你仔仔細細的查看了實現代碼,發(fā)現就是簡單的 SPI 通信,基本上都是判斷、賦值操作,還有就是使用循環(huán)等待標志位(使用硬件 SPI),。
(或許你會懷疑循環(huán)等等標志代碼導致了時間的不確定,但我的第一直覺告訴我不是它,因為 SPI 的通信時間是可控的(只要器件正常,從機一定會返回數據),STM32F1 系列的硬件 SPI 通信魚鷹也用了五六年,不應該有問題才對)
這些我全部防出去了昂(甚至魚鷹都考慮到線程執(zhí)行可能受到中斷的影響,特地在問題代碼執(zhí)行期間禁止了中斷)。
沒辦法,我只能停停,放下源碼本身的分析,拿出了殺手锏:《KEIL 下如何準確測量代碼執(zhí)行時間?》開始對問題代碼進行時間測量。
有備而來
經過幾番測量,很快昂,定位到類似下面的代碼:

發(fā)現竟然在52到57行之間花費了大量時間。就一些局部變量的定義,唯一和傳統(tǒng)寫法不同的是使用了動態(tài)數組,怎么會花費這么多時間?
按照傳統(tǒng)寫法,這里應該使用固定大小的數組。
我大意了啊,沒有閃,當時移植這份代碼的時候就留意到了這個另類寫法,當時還特地看了一下實現,但最終還是栽在了這里。
這段代碼是亂打(寫)的嗎?他可不是亂打(寫)的,格式清晰、移植方便、還有各種異常處理,明顯有備而來。
來、騙,來、偷襲我這經驗豐富的老同志。
這好嗎?這不好,我勸他耗子尾汁。
尋根問底
事實上,如果對時間要求不是很高的話,這段代碼不會有任何問題,它的基本讀取功能是沒有任何問題的,只是說它的執(zhí)行時間很不穩(wěn)定,有的時候幾十微秒就可以執(zhí)行完畢,有時候可能需要幾毫秒時間,還有極端的可能是直接死機(Hardfault)!
那么動態(tài)數組是如何實現的,或者說它的本質是什么呢?
本質就是使用 malloc 函數申請堆空間(在 rt-thread 中又會調用 rt_malloc),然后在離開函數前使用 free 函數釋放堆空間。
可以查看匯編確認:


看到這里,你也就知道為什么會出現之前的現象了吧。
系統(tǒng)未完全運行前,很少有線程申請堆空間,所以執(zhí)行時間比較穩(wěn)定,因為它能快速的找到合適的內存塊,一旦系統(tǒng)里所有線程正式工作了,涉及到大量的內存申請與釋放,有大量的內存碎片,也就不容易找到合適的內存,這樣執(zhí)行時間也就不穩(wěn)定了,這對于實時要求高的功能是一個災難。
所以,如果你的功能不要求實時性的話,使用動態(tài)數組是可以的,一旦你的功能要求實時性,那么使用靜態(tài)數組才是更好的選擇(如果使用靜態(tài)數組,一定要注意使用范圍,最好加上斷言機制),如果代碼是在中斷執(zhí)行,rt-thread系統(tǒng)中,則必須使用靜態(tài)數組,否則 rt_malloc 無法正常執(zhí)行(斷言失敗)。
你防住了嗎?
*博客內容為網友個人發(fā)布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。











