字體:小 中 大 | |
|
|
2008/10/13 11:29:32瀏覽5561|回應0|推薦2 | |
小型MCU的C compiler雖然很方便,但實際上,與PC上寫C還是有一段距離,其實不管哪顆mcu,例如像8051這麼囉唆的記憶體設計,大致上都會碰到一個東西,就是:overlay system(memory),有的compiler很好心,會警告我們不穩定,但大多數人的習慣,都會選擇忽略警告訊息(warning),尤其是一些看不懂的訊息,上述overlay就是一個例子,大概10個有9.9個會略過這個警告,因為搞不清楚overlay什麼東西?為什麼overlay?反正compiler會過,程式也會動。 以8051為例,由於內部記憶體很小,只有256bytes,各種variable含stack一起共用,compiler為了要能夠完整控制這一點點記憶體,幹了件pc compiler不會幹的事,就是把local variable從堆疊中移除,變成絕對定址,既然local variable不放在堆疊中,堆疊就不需要預留一大片在那裡佔空間,此時所有byte都控制的到,最佳化也才可能辦的到,但這個特點卻成為純軟體工程師的陷阱。 有技巧的工程師都喜歡用【function pointer】,就是:(*func)(); 問題來了,compiler根本無法預測到程式會在哪個狀況載入這個function pointer,此時又是絕對定址,那恐怖了,local variable便有可能發生誤蓋的問題,呼叫某個函數完成後,local variable竟然變了,如下面例子:
void (*func)(void); Void main(void) { Func = callback; …… Others(); } // 外部中斷服務函式,compiler總猜不到何時IO會動作了吧! void External_ISR(void) { (*func)(); } void callback(void) { … } void others(void) { char Var = 5; Exec_somefunc(); Var=Var+1; // Var正確應該是6,但因為之前的Var被蓋掉了,這行執行結束,就出現奇怪的答案。 } 此時便會發生不可思議,想破頭也找不到答案的狀況了,因為完全是隨機的,此時便會有一些古怪的歸納會被發表出來。 正確解法是要告訴compiler這個function不要這樣搞,請把local variable放在堆疊內: reentrant void callback(void) { … } 遞迴呼叫(就是自己叫自己)也要使用這個關鍵字。 關鍵字:reentrant是IAR的,keil也一樣,基本上小型MCU的compiler都應該會有類似的關鍵字,不這樣做,設計了一大堆漂亮的function table,魚沒吃到,反倒弄了一身腥。 完全使用reentrant其實也是一件麻煩事,因為許多附送的standard lib並不是使用reentrant來編譯的,例如sprintf在Keil c就不是reentrant,除非進行函式庫的修改,否則一樣會因為這個原因遭遇許多奇怪的問題。 人有時要學著轉彎,此時就要換一個方法了,退一下,稍微浪費一點資源又有什麼關係呢?為了解這個問題費盡心力加班,搞定了,也不過就是sprintf罷了,也不會登天,如果能像古代的黃帝一樣,修煉有成,拔宅飛昇,不只老婆小孩,連螞蟻老鼠都一起變成了神仙,這種心力花下去我還覺得有意義,如果搞了老半天,只是sprintf很漂亮,那我想是大可不必的。 這個時候只要把optimization的選項設定成不要data overlaying,換句話說,只讓compiler做dead code elimination以下的最佳化動作,雖然機械碼變的沒那麼棒,檔案大小也變大了,速度也沒那麼快,可是程式正確了,以現在ic的技術,只要把時脈提升,flash放大一點,其實也是可以達到一樣的效果。 這樣的想法個人認為很重要,因為你費盡心力的下場,如果具有一定的延續性,就是將來也用的到,那也就罷了,如果今天花了很多時間搞了,一年後flash便宜了,ram也便宜了,同樣的把戲就失效了,可是又只會這樣的把戲,到時就只能【為賦新辭強說愁】了,如果又當上了主管,屆時就會強迫下屬【說愁】,那就不太好了。 如果要把精神用在這種地方,那我會建議還不如去仔細研究組合語言,因為只要有cpu,就一定會有組合語言,能夠使用組合語言寫出很好的函式庫,尤其是現在演算法又很重要,把精神花在這裡反而比較好,不過我會覺得ic的design house會是比較適當的地方,如果今天是一個以製造為主的公司,人家一定會認為這些都是放屁。 |
|
( 心情隨筆|心情日記 ) |