在C語言裡面提供了函數指針,我認為它比較重要的功能就是用來提供接口,使得C語言可以模擬面向對象的語言為某些功能提供接口,實現功能代碼的隔離。 這不,前些日子寫了個小程序,用C51寫的,其中有個功能就是操作液晶屏,在上面顯示菜單、輸出結果什麼的。在我看來,這部分功能使用函數指針最好不過了。譬如,不管是什麼菜單,總得要顯示出來吧,定義一個show()接口就好。這樣上層代碼很簡單,反正對每個菜單都調用其show()接口,它們自己完成顯示。 嗯,可是想法是好的,結果是不妙的。首先這麼做在C51的語法層面上沒有任何問題,編譯後沒有任何錯誤。可是實際運行時就發現一些奇怪的現象,譬如調用某個函數,明明入口參數的值是100,跑到函數內部就莫名其妙的變成了其它的值了。弄了好久不得其解。 最後沒有辦法,切換keil到匯編模式,這才發現出問題,入口參數的變量地址與某個全局變量的地址重了。在這裡說一點題外話,我做的那個小東西用的是8031,片內變量空間才128個,這裡面還包括了堆棧段的空間,因此我輕易不定義片內變量,大部分情況下都是使用片外變量,反正對於我做的那個東西,片外變量的速度也是足夠了。 現在的問題是入口參數變量的地址怎麼會跟其它全局變量的地址重復了呢?我仔細分析了半天,發現原來是函數指針惹的禍。如果沒有函數指針,程序裡的每個函數都有直接被調用的代碼,譬如初始菜單顯示,可能就會調用startMenuShow()函數。但是現在變成了函數指針,顯示初始菜單的代碼就變成了menu[START].show()。也就是說編譯器在編譯的時候並不知道startMenuShow()函數在哪裡被調用,而只有在運行的時候才知道是哪一個函數。 本來如果沒有函數指針,編譯器能夠構建出一個完整的函數調用樹,並且根據這個樹完成變量空間的分配。現在有了函數指針,這個函數調用樹就跟實際的情況不一樣了,編譯器的優化導致有些變量空間就重復。譬如在C51中有如下代碼: unsigned short c_add(unsigned char xdata a, unsigned char xdata b) { a++; b++; return (a + b); } void main(void) { unsigned char xdata score1, score2; unisgned short xdata total; score1 = 100; score2 = 80; total = c_add(score1, score2); } 在上面的代碼中,函數c_add()的兩個形參a、 b與main()函數的實參score1、score2的變量地址是不一樣的,因為在main()函數中,調用完c_add()函數後,score1和score2這兩個變量可能還有後續調用,因此編譯器不會對這四個變量定義到同一個位置。 但是如果main()函數中采用函數指針調用c_add()函數,那麼編譯器就不知道main()函數中調用的函數到底是什麼,出於代碼優化的考慮,編譯器為了節省變量空間,當它發現c_add()函數和main()函數沒有直接、間接調用關系,那麼它就可以把c_add()的兩個形參a、 b與main()函數的實參score1、score2的變量地址定義到同一地址。 因為這四個變量都分別是本函數的局部變量,相互之間又沒有直接調用關系,就算是地址重復了,也不會導致邏輯上有什麼問題。這樣定義可以節省程序變量所占用的空間,對於嵌入式系統來說,資源一般都是有限的,能省則省。編譯器的優化缺省設置一般都是比較高的,我也不建議為了函數指針調整優化級別,畢竟優化帶來的好處還是有一些的。 綜上所述,我的結論就是在C51開發程序的時候,應該盡量避免使用函數指針,如果一定要用,也應該避免使用形參、局部變量,以免因為地址重復給程序造成一些不可預料的後果,而這些問題查找起來是非常困難的。 當然,如果用的不是C51,我所知道的在嵌入式開發中,在ARM9系統下使用的C語言,對於函數指針的支持是沒有問題的,我的另外一個系統就大量使用了函數指針。
本文出自 “rainman” 博客,請務必保留此出處http://lancelot.blog.51cto.com/393579/224373