王國斌
(福州大觀電子科技有限公司 研發(fā)部,福州 350003)
隨著嵌入式產(chǎn)品應用環(huán)境和功能的日趨復雜,應用工程師都希望能夠把更多的精力放在應用層次上應用功能的開發(fā)和穩(wěn)定,而花更少的精力來關注底層驅(qū)動軟件的實現(xiàn)和細節(jié)?;诖四康?,一個比較好的實現(xiàn)方法就是在開發(fā)當中能夠把已經(jīng)穩(wěn)定和充分測試的底層驅(qū)動軟件的平臺框架放在一個工程中編譯當做“固化”,而把應用程序的功能實現(xiàn)部分放在另外一個工程中編譯,從而減少驅(qū)動軟件和應用軟件的耦合度。這就有點像在編寫PC軟件時,我們編寫一定的應用功能,而底層的驅(qū)動等則通過調(diào)用DLL的方式去實現(xiàn)。
在了解怎樣實現(xiàn)平臺程序和應用程序的分開編譯之前,先來學習一個重要的知識——指針和函數(shù)指針。
1.1.1 指 針
C語言的指針概念是最令C語言初學者頭疼的問題,甚至一些有工作經(jīng)驗的程序員在平時的工作當中也是害怕它和規(guī)避它的。然而,如果沒有指針,那C語言的效率和編程的樂趣將大打折扣。正由于此,C語言的指針問題也是很多公司面試時技術面試官必提的一個問題。指針,實際上應該叫做“地址”比較合理,這個地址表明了你所需要的數(shù)據(jù)或者程序段在整個地址空間的位置。通過這個地址你可以找到你所需要的東西的位置。它就像我們實際生活中的門牌號,比如說我們的實驗樓在C區(qū)18號,那么只要找到了C區(qū)18號也就找到了我們的實驗樓。
指針就類似于一個地址編碼,那這個編碼當然也是有規(guī)定長度的,或者說是有大小的。就像我們給倉庫的儲存格子來編碼,那么這個編碼的長度和內(nèi)容就決定了最多能描述多少個格子。當然這個指針的長度越長,那么用它來描述的地址信息量就越多,也就是具有更大的選址空間。因此在16位MCU中指針的大小往往就是只有16位,而在32位MCU中指針的大小就往往是32位。一般來說在同一個地址空間內(nèi)指針的大小是固定的,而這就是為什么在同一個MCU(特別是馮·諾伊曼結(jié)構(gòu))上,無論指針指向什么類型的數(shù)據(jù),它總是占用固定的字節(jié)數(shù)。
(1)指針的類型
還是拿我們實驗樓地址為例子:C區(qū)18號它既可以代表是一系列實驗室的集合,也可以代表一堆房子的集合,而它具體可以代表什么樣的含義就看你使用的時候需要怎樣的信息。同樣在程序中的一個地址,我們可以看作是一個字節(jié)數(shù)組的集合的開始,也可以看作是一個自定義的結(jié)構(gòu)體的開始,當然還可以看作是一段程序段的開始。這就是我們在編程的時候可以把不同數(shù)據(jù)類型的指針相互轉(zhuǎn)換的原因。
(2)指向指針的指針
很多人對指針已經(jīng)很頭痛了,對指向指針的指針可能就更加不知南北了。其實指向指針的指針在我們生活中運用得很多,比如說你要通過張三要找王五,但是他不知道王五在哪里,但是他知道李四知道王五在哪里,而他自己又知道李四在哪里,這樣你就可以通過張三這個指針指向李四,而通過李四這個指針來指向王五來找到王五,而張三就是一個指向王五的指針的指針。相應的程序如下:
1.1.2 函數(shù)指針
如果看明白了上面這些描述,那也就應該知道函數(shù)指針的含義了。函數(shù)指針就是一個指向一段程序的地址,它和數(shù)據(jù)指針在本質(zhì)上是沒有區(qū)別的。
通過函數(shù)指針調(diào)用函數(shù)和直接調(diào)用函數(shù)的區(qū)別就在于我們事先是否知道這個函數(shù)的地址。比如我要把一封信送給李四,但是我不知道李四住在哪里,然而我知道張三知道李四在哪里,這樣我就先把信轉(zhuǎn)交給張三,信里面的內(nèi)容就是我要傳遞給李四的信息(就是我們函數(shù)調(diào)用的參數(shù)),然后張三把信轉(zhuǎn)交給了李四,李四根據(jù)信里面的內(nèi)容進行相應的處理,并給我一個答案,而這個時候他已經(jīng)知道了我的地址,因此他就可以直接繞過張三把答案回復給我。利用函數(shù)指針調(diào)用函數(shù)的過程就和上面所舉的例子一樣,而它與直接調(diào)用函數(shù)的區(qū)別就在于多了張三這一道手續(xù)而已。
函數(shù)指針的舉例描述:
①uint8(*)(uint8,uint8)
表示一個函數(shù)指針。這個函數(shù)有兩個參數(shù),參數(shù)類型均為uint8,函數(shù)的返回類型是uint8。
②void(*const)(uint8,uint8(*)(void))
表示一個常量函數(shù)指針。這個函數(shù)有兩個參數(shù),第一個參數(shù)的類型是uint8,第二個參數(shù)的類型是一個函數(shù)指針,而這個函數(shù)指針的參數(shù)沒有,返回類型則是uint8,最外層的函數(shù)指針的返回類型則為void。
前面講述了指針,下面我們講述指針的前提,也就是地址空間。在馮·諾依曼結(jié)構(gòu)的CPU中只有一個地址空間,無論是程序還是數(shù)據(jù)都可以在這個空間內(nèi)被找到,因此在編程的時候從嚴格意義上來講就只存在一種類型的指針,那就是void*。而對于哈佛結(jié)構(gòu)的CPU來說則要顯得復雜一些,程序空間和數(shù)據(jù)空間是隔離的,因此就存在指向程序空間和數(shù)據(jù)空間的兩種指針。51單片機這種哈佛結(jié)構(gòu)的增強則要顯得更加復雜,不但程序空間和數(shù)據(jù)空間不統(tǒng)一,而且數(shù)據(jù)空間還要分成幾種類型。因此從C語言編程的復雜度來講,馮·諾依曼結(jié)構(gòu)的CPU要簡單一些。
簡單地說,程序就是一段二進制碼0和1的有序組合。而這種有序組合構(gòu)成了目標CPU的一條條機器指令,這一系列的機器指令的有機組合則構(gòu)成了程序員為之驕傲的程序。當我們的程序被目標CPU開始執(zhí)行之后,它就按照預先安排的步驟去完成各種各樣的事情。而函數(shù)就是程序中的一個片斷,它在程序中的某一個位置完成一件或者幾件我們所需要完成的事情。
然而如果你調(diào)用的函數(shù)并不在你所編寫的程序段中,而已經(jīng)在目標CPU的地址空間的某個地方了,那該怎么辦呢?這個時候我們就需要用函數(shù)指針來完成這件事情了,首先需要通過一種方式來獲得這個函數(shù)指針,然后通過函數(shù)指針來調(diào)用你所需要的函數(shù)。
程序的編譯就是把C語言文件編譯成機器碼,能讓目標CPU通過這種方式和你交流。而編譯完的程序段放在目標CPU地址空間的什么位置就需要由鏈接來決定。目前,絕大多數(shù)的編譯器都允許用戶自定義鏈接文件,因此通過這種方式我們就能按照意愿來安排代碼的位置了。如果某些代碼已經(jīng)很穩(wěn)定了,就可以事先把它固定在目標CPU的地址空間的某些位置,從而減少后期的工作量。
當平臺程序和應用程序分離之后它們之間的接口就只能通過函數(shù)指針的方式來實現(xiàn)了。這里應用程序需要把它的入口函數(shù)的地址放在固定的位置,而平臺程序則需要把它的API函數(shù)的地址放在固定的位置。
比如我們把應用程序的函數(shù)入口地址放在0x10000這個位置,那么平臺程序運行的時候就從0x10000地址取出一個指針(地址),然后調(diào)用這個指針所指向的程序段來運行應用程序。應用程序通過同樣的方式能夠調(diào)用平臺程序的API函數(shù)來完成預先定義的功能。
本文主要是根據(jù)日常的工程項目應用的實際需求,整理出了一套可以在設計嵌入式系統(tǒng)軟件時對平臺程序和應用程序分開編譯的一種方法,希望對相關技術人員有所幫助。
[1] 陳建輝.C語言指針探討[J].莆田高等專科學校學報,2001,8(4).
[2] 趙森,李卓民.C程序設計[M].北京:冶金工業(yè)出版社,2005.