C語言在嵌入式系統(tǒng)編程時的注意事項(二)

姓名:徐嬌 ? ?學(xué)號:17011210547

轉(zhuǎn)自http://mp.weixin.qq.com/s/YUXrJbin_rnwModTY2Ds_A

【嵌牛導(dǎo)讀】

C語言是一門通用計算機(jī)編程語言,應(yīng)用廣泛。C語言的設(shè)計目標(biāo)是提供一種能以簡易的方式編譯、處理低級存儲器、產(chǎn)生少量的機(jī)器碼以及不需要任何運(yùn)行環(huán)境支持便能運(yùn)行的編程語言。

盡管C語言提供了許多低級處理的功能,但仍然保持著良好跨平臺的特性,以一個標(biāo)準(zhǔn)規(guī)格寫出的C語言程序可在許多電腦平臺上進(jìn)行編譯,甚至包含一些嵌入式處理器(單片機(jī)或稱MCU)以及超級電腦等作業(yè)平臺。

20世紀(jì)80年代,為了避免各開發(fā)廠商用的C語言語法產(chǎn)生差異,由美國國家標(biāo)準(zhǔn)局為C語言訂定了一套完整的國際標(biāo)準(zhǔn)語法,稱為ANSI C,作為C語言最初的標(biāo)準(zhǔn)。

【嵌牛鼻子】C語言、嵌入式系統(tǒng)編程、注意事項之內(nèi)存操作

【嵌牛提問】C語言在嵌入式系統(tǒng)編程時的注意事項

【嵌牛正文】

C語言嵌入式系統(tǒng)編程注意事項之內(nèi)存操作

在嵌入式系統(tǒng)的編程中,常常要求在特定的內(nèi)存單元讀寫內(nèi)容,匯編有對應(yīng)的MOV指令,而除C/C++以外的其它編程語言基本沒有直接訪問絕對地址的能力

數(shù)據(jù)指針

在嵌入式系統(tǒng)的編程中,常常要求在特定的內(nèi)存單元讀寫內(nèi)容,匯編有對應(yīng)的MOV指令,而除C/C++以外的其它編程語言基本沒有直接訪問絕對地址的能力。在嵌入式系統(tǒng)的實(shí)際調(diào)試中,多借助C語言指針?biāo)哂械膶^對地址單元內(nèi)容的讀寫能力。以指針直接操作內(nèi)存多發(fā)生在如下幾種情況:

(1) 某I/O芯片被定位在CPU的存儲空間而非I/O空間,而且寄存器對應(yīng)于某特定地址;

(2) 兩個CPU之間以雙端口RAM通信,CPU需要在雙端口RAM的特定單元(稱為mail box)書寫內(nèi)容以在對方CPU產(chǎn)生中斷;

(3) 讀取在ROM或FLASH的特定單元所燒錄的漢字和英文字模。

譬如:

unsigned char *p =(unsigned char *)0xF000FF00;

*p=11;

以上程序的意義為在絕對地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)寫入11。

在使用絕對地址指針時,要注意指針自增自減操作的結(jié)果取決于指針指向的數(shù)據(jù)類別。上例中p++后的結(jié)果是p= 0xF000FF01,若p指向int,即:

int *p =(int *)0xF000FF00;

p++(或++p)的結(jié)果等同于:p = p+sizeof(int),而p-(或-p)的結(jié)果是p = p-sizeof(int)。

同理,若執(zhí)行:

long int *p =(long int *)0xF000FF00;

則p++(或++p)的結(jié)果等同于:p = p+sizeof(long int) ,而p-(或-p)的結(jié)果是p = p-sizeof(long int)。

記住:CPU以字節(jié)為單位編址,而C語言指針以指向的數(shù)據(jù)類型長度作自增和自減。理解這一點(diǎn)對于以指針直接操作內(nèi)存是相當(dāng)重要的。

函數(shù)指針

首先要理解以下三個問題:

(1)C語言中函數(shù)名直接對應(yīng)于函數(shù)生成的指令代碼在內(nèi)存中的地址,因此函數(shù)名可以直接賦給指向函數(shù)的指針;

(2)調(diào)用函數(shù)實(shí)際上等同于“調(diào)轉(zhuǎn)指令+參數(shù)傳遞處理+回歸位置入?!保举|(zhì)上最核心的操作是將函數(shù)生成的目標(biāo)代碼的首地址賦給CPU的PC寄存器;

(3)因為函數(shù)調(diào)用的本質(zhì)是跳轉(zhuǎn)到某一個地址單元的code去執(zhí)行,所以可以“調(diào)用”一個根本就不存在的函數(shù)實(shí)體,暈?請往下看:

請拿出你可以獲得的任何一本大學(xué)《微型計算機(jī)原理》教材,書中講到,186 CPU啟動后跳轉(zhuǎn)至絕對地址0xFFFF0(對應(yīng)C語言指針是0xF000FFF0,0xF000為段地址,0xFFF0為段內(nèi)偏移)執(zhí)行,請看下面的代碼:

typedef void(*lp) ( ); /*定義一個無參數(shù)、無返回類型的*/

/*函數(shù)指針類型*/

lp lpReset =(lp)0xF000FFF0; /*定義一個函數(shù)指針,指向*/

/* CPU啟動后所執(zhí)行第一條指令的位置*/

lpReset(); /*調(diào)用函數(shù)*/

在以上的程序中,我們根本沒有看到任何一個函數(shù)實(shí)體,但是我們卻執(zhí)行了這樣的函數(shù)調(diào)用:lpReset(),它實(shí)際上起到了“軟重啟”的作用,跳轉(zhuǎn)到CPU啟動后第一條要執(zhí)行的指令的位置。

記?。汉瘮?shù)無它,唯指令集合耳;你可以調(diào)用一個沒有函數(shù)體的函數(shù),本質(zhì)上只是換一個地址開始執(zhí)行指令!

數(shù)組vs動態(tài)申請

在嵌入式系統(tǒng)中動態(tài)內(nèi)存申請存在比一般系統(tǒng)編程時更嚴(yán)格的要求,這是因為嵌入式系統(tǒng)的內(nèi)存空間往往是十分有限的,不經(jīng)意的內(nèi)存泄露會很快導(dǎo)致系統(tǒng)的崩潰。

所以一定要保證你的malloc和free成對出現(xiàn),如果你寫出這樣的一段程序:

char *(void)

{

char *p;

p =(char *)malloc(…);

if(p==NULL)

…;

… /*一系列針對p的操作*/

return p;

}

在某處調(diào)用(),用完中動態(tài)申請的內(nèi)存后將其free,如下:

char *q =();

free(q);

上述代碼明顯是不合理的,因為違反了malloc和free成對出現(xiàn)的原則,即“誰申請,就由誰釋放”原則。不滿足這個原則,會導(dǎo)致代碼的耦合度增大,因為用戶在調(diào)用函數(shù)時需要知道其內(nèi)部細(xì)節(jié)!

正確的做法是在調(diào)用處申請內(nèi)存,并傳入函數(shù),如下:

char *p=malloc(…);

if(p==NULL)

…;

(p);

free(p);

p=NULL;

而函數(shù)則接收參數(shù)p,如下:

void(char *p)

{

… /*一系列針對p的操作*/

}

基本上,動態(tài)申請內(nèi)存方式可以用較大的數(shù)組替換。對于編程新手,筆者推薦你盡量采用數(shù)組!嵌入式系統(tǒng)可以以博大的胸襟接收瑕疵,而無法“海納”錯誤。畢竟,以最笨的方式苦練神功的郭靖勝過機(jī)智聰明卻范政治錯誤走反革命道路的楊康。

給出原則:

(1)盡可能的選用數(shù)組,數(shù)組不能越界訪問(真理越過一步就是謬誤,數(shù)組越過界限就光榮地成全了一個混亂的嵌入式系統(tǒng));

(2)如果使用動態(tài)申請,則申請后一定要判斷是否申請成功了,并且malloc和free應(yīng)成對出現(xiàn)!

在嵌入式系統(tǒng)的編程中,常常要求在特定的內(nèi)存單元讀寫內(nèi)容,匯編有對應(yīng)的MOV指令,而除C/C++以外的其它編程語言基本沒有直接訪問絕對地址的能力

關(guān)鍵字const

const意味著“只讀”。區(qū)別如下代碼的功能非常重要,也是老生長嘆,如果你還不知道它們的區(qū)別,而且已經(jīng)在程序界摸爬滾打多年,那只能說這是一個悲哀:

const int a;

int const a;

const int *a;

int * const a;

int const * a const;

(1) 關(guān)鍵字const的作用是為給讀你代碼的人傳達(dá)非常有用的信息。例如,在函數(shù)的形參前添加const關(guān)鍵字意味著這個參數(shù)在函數(shù)體內(nèi)不會被修改,屬于“輸入?yún)?shù)”。在有多個形參的時候,函數(shù)的調(diào)用者可以憑借參數(shù)前是否有const關(guān)鍵字,清晰的辨別哪些是輸入?yún)?shù),哪些是可能的輸出參數(shù)。

(2)合理地使用關(guān)鍵字const可以使編譯器很自然地保護(hù)那些不希望被改變的參數(shù),防止其被無意的代碼修改,這樣可以減少bug的出現(xiàn)。

const在C++語言中則包含了更豐富的含義,而在C語言中僅意味著:“只能讀的普通變量”,可以稱其為“不能改變的變量”(這個說法似乎很拗口,但卻最準(zhǔn)確的表達(dá)了C語言中const的本質(zhì)),在編譯階段需要的常數(shù)仍然只能以#define宏定義!故在C語言中如下程序是非法的:

const int SIZE = 10;

char a[SIZE]; /*非法:編譯階段不能用到變量*/

關(guān)鍵字volaTIle

C語言編譯器會對用戶書寫的代碼進(jìn)行優(yōu)化,譬如如下代碼:

int a,b,c;

a = inWord(0x100); /*讀取I/O空間0x100端口的內(nèi)容存入a變量*/

b = a;

a = inWord(0x100); /*再次讀取I/O空間0x100端口的內(nèi)容存入a變量*/

c = a;

很可能被編譯器優(yōu)化為:

int a,b,c;

a = inWord(0x100); /*讀取I/O空間0x100端口的內(nèi)容存入a變量*/

b = a;

c = a;

但是這樣的優(yōu)化結(jié)果可能導(dǎo)致錯誤,如果I/O空間0x100端口的內(nèi)容在執(zhí)行第一次讀操作后被其它程序?qū)懭胄轮?,則其實(shí)第2次讀操作讀出的內(nèi)容與第一次不同,b和c的值應(yīng)該不同。在變量a的定義前加上volaTIle關(guān)鍵字可以防止編譯器的類似優(yōu)化,正確的做法是:

volatile int a;

volatile變量可能用于如下幾種情況:

(1) 并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器,例中的代碼屬于此類);

(2) 一個中斷服務(wù)子程序中會訪問到的非自動變量(也就是全局變量);

(3) 多線程應(yīng)用中被幾個任務(wù)共享的變量。

CPU字長與存儲器位寬不一致處理

在背景篇中提到,本文特意選擇了一個與CPU字長不一致的存儲芯片,就是為了進(jìn)行本節(jié)的討論,解決CPU字長與存儲器位寬不一致的情況。80186的字長為16,而NVRAM的位寬為8,在這種情況下,我們需要為NVRAM提供讀寫字節(jié)、字的接口,如下:

typedef unsigned char BYTE;

typedef unsigned int WORD;

/*函數(shù)功能:讀NVRAM中字節(jié)

*參數(shù):wOffset,讀取位置相對NVRAM基地址的偏移

*返回:讀取到的字節(jié)值

*/

extern BYTE ReadByteNVRAM(WORD wOffset)

{

LPBYTE lpAddr =(BYTE*)(NVRAM + wOffset * 2); /*為什么偏移要×2?*/

return *lpAddr;

}

/*函數(shù)功能:讀NVRAM中字

*參數(shù):wOffset,讀取位置相對NVRAM基地址的偏移

*返回:讀取到的字

*/

extern WORD ReadWordNVRAM(WORD wOffset)

{

WORD wTmp = 0;

LPBYTE lpAddr;

/*讀取高位字節(jié)*/

lpAddr =(BYTE*)(NVRAM + wOffset * 2); /*為什么偏移要×2?*/

wTmp +=(*lpAddr)*256;

/*讀取低位字節(jié)*/

lpAddr =(BYTE*)(NVRAM +(wOffset +1)* 2); /*為什么偏移要×2?*/

wTmp += *lpAddr;

return wTmp;

}

/*函數(shù)功能:向NVRAM中寫一個字節(jié)

*參數(shù):wOffset,寫入位置相對NVRAM基地址的偏移

* byData,欲寫入的字節(jié)

*/

extern void WriteByteNVRAM(WORD wOffset,BYTE byData)

{

}

/*函數(shù)功能:向NVRAM中寫一個字*/

*參數(shù):wOffset,寫入位置相對NVRAM基地址的偏移

* wData,欲寫入的字

*/

extern void WriteWordNVRAM(WORD wOffset,WORD wData)

{

}

子貢問曰:Why偏移要乘以2?

子曰:16位80186與8位NVRAM之間互連只能以地址線A1對其A0,CPU本身的A0與NVRAM不連接。因此,NVRAM的地址只能是偶數(shù)地址,故每次以0x10為單位前進(jìn)!

子貢再問:So why 80186的地址線A0不與NVRAM的A0連接?

子曰:請看《IT論語》之《微機(jī)原理篇》,那里面講述了關(guān)于計算機(jī)組成的圣人之道。

總結(jié)

本篇主要講述了嵌入式系統(tǒng)C編程中內(nèi)存操作的相關(guān)技巧。掌握并深入理解關(guān)于數(shù)據(jù)指針、函數(shù)指針、動態(tài)申請內(nèi)存、const及volatile關(guān)鍵字等的相關(guān)知識,是一個優(yōu)秀的C語言程序設(shè)計師的基本要求。當(dāng)我們已經(jīng)牢固掌握了上述技巧后,我們就已經(jīng)學(xué)會了C語言的99%,因為C語言最精華的內(nèi)涵皆在內(nèi)存操作中體現(xiàn)。

我們之所以在嵌入式系統(tǒng)中使用C語言進(jìn)行程序設(shè)計,99%是因為其強(qiáng)大的內(nèi)存操作能力!

如果你愛編程,請你愛C語言;

如果你愛C語言,請你愛指針;

如果你愛指針,請你愛指針的指針!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容