數據指針
在嵌入式系統的編程中,常常要求在特定的內存單元讀寫內容,匯編有對應的MOV指令,而除C/C++以外的其它編程語言基本沒有直接訪問絕對地址的能力。在嵌入式的實際調試中,多借助C語言指針所具有的對絕對地址單元內容的讀寫能力。以指針直接操作內存多發生在如下幾種情況:
(1) 某I/O芯片被定位在CPU的存儲空間而非I/O空間,而且寄存器對應於某特定地址;
(2) 兩個CPU之間以雙端口RAM通信,CPU需要在雙端口RAM的特定單元(稱為mail box)書寫內容以在對方CPU產生中斷;
(3) 讀取在ROM或FLASH的特定單元所燒錄的漢字和英文字模。
譬如:
unsigned char *p = (unsigned char *)0xF000FF00;
*p=11;
以上程序的意義為在絕對地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)寫入11。
在使用絕對地址指針時,要注意指針自增自減操作的結果取決於指針指向的數據類別。上例中p++後的結果是p= 0xF000FF01,若p指向int,即:
int *p = (int *)0xF000FF00;
p++(或++p)的結果等同於:p = p+sizeof(int),而p-(或-p)的結果是p = p-sizeof(int)。
同理,若執行:
long int *p = (long int *)0xF000FF00;
則p++(或++p)的結果等同於:p = p+sizeof(long int) ,而p-(或-p)的結果是p = p-sizeof(long int)。
記住:CPU以字節為單位編址,而C語言指針以指向的數據類型長度作自增和自減。理解這一點對於以指針直接操作內存是相當重要的。
函數指針
首先要理解以下三個問題:
(1)C語言中函數名直接對應於函數生成的指令代碼在內存中的地址,因此函數名可以直接賦給指向函數的指針;
(2)調用函數實際上等同於"調轉指令+參數傳遞處理+回歸位置入棧",本質上最核心的操作是將函數生成的目標代碼的首地址賦給CPU的PC寄存器;
(3)因為函數調用的本質是跳轉到某一個地址單元的code去執行,所以可以"調用"一個根本就不存在的函數實體,暈?請往下看:
請拿出你可以獲得的任何一本大學《微型計算機原理》教材,書中講到,186 CPU啟動後跳轉至絕對地址0xFFFF0(對應C語言指針是0xF000FFF0,0xF000為段地址,0xFFF0為段內偏移)執行,請看下面的代碼:
typedef void (*lpFunction) ( ); /* 定義一個無參數、無返回類型的 */
/* 函數指針類型 */
lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定義一個函數指針,指向*/
/* CPU啟動後所執行第一條指令的位置 */
lpReset(); /* 調用函數 */
在以上的程序中,我們根本沒有看到任何一個函數實體,但是我們卻執行了這樣的函數調用:lpReset(),它實際上起到了"軟重啟"的作用,跳轉到CPU啟動後第一條要執行的指令的位置。
記住:函數無它,唯指令集合耳;你可以調用一個沒有函數體的函數,本質上只是換一個地址開始執行指令!
數組vs.動態申請
在嵌入式中動態內存申請存在比一般系統編程時更嚴格的要求,這是因為嵌入式系統的內存空間往往是十分有限的,不經意的內存洩露會很快導致系統的崩潰。
所以一定要保證你的malloc和free成對出現,如果你寫出這樣的一段程序:
char * function(void)
{
char *p;
p = (char *)malloc(…);
if(p==NULL)
…;
… /* 一系列針對p的操作 */
return p;
}