數(shù)組vs.動態(tài)申請
在嵌入式系統(tǒng)中動態(tài)內(nèi)存申請存在比一般系統(tǒng)編程時(shí)更嚴(yán)格的要求,這是因?yàn)榍度胧较到y(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);
上述代碼明顯是不合理的,因?yàn)檫`反了malloc和free成對出現(xiàn)的原則,即“誰申請,就由誰釋放”原則。不滿足這個(gè)原則,會導(dǎo)致代碼的耦合度增大,因?yàn)橛脩粼谡{(diào)用函數(shù)時(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)可以以博大的胸襟接收瑕疵,而無法“海納”錯(cuò)誤。畢竟,以最笨的方式苦練神功的郭靖勝過機(jī)智聰明卻范政治錯(cuò)誤走反革命道路的楊康。
給出原則:
(1)盡可能的選用數(shù)組,數(shù)組不能越界訪問(真理越過一步就是謬誤,數(shù)組越過界限就光榮地成全了一個(gè)混亂的嵌入式系統(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)在程序界摸爬滾打多年,那只能說這是一個(gè)悲哀:
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)鍵字意味著這個(gè)參數(shù)在函數(shù)體內(nèi)不會被修改,屬于“輸入?yún)?shù)”。在有多個(gè)形參的時(shí)候,函數(shù)的調(diào)用者可以憑借參數(shù)前是否有const關(guān)鍵字,清晰的辨別哪些是輸入?yún)?shù),哪些是可能的輸出參數(shù)。
(2)合理地使用關(guān)鍵字const可以使編譯器很自然地保護(hù)那些不希望被改變的參數(shù),防止其被無意的代碼修改,這樣可以減少bug的出現(xiàn)。
const在C++語言中則包含了更豐富的含義,而在C語言中僅意味著:“只能讀的普通變量”,可以稱其為“不能改變的變量”(這個(gè)說法似乎很拗口,但卻最準(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)致錯(cuò)誤,如果I/O空間0x100端口的內(nèi)容在執(zhí)行第一次讀操作后被其它程序?qū)懭胄轮担瑒t其實(shí)第2次讀操作讀出的內(nèi)容與第一次不同,b和c的值應(yīng)該不同。在變量a的定義前加上volatile關(guān)鍵字可以防止編譯器的類似優(yōu)化,正確的做法是:
volatile int a;
volatile變量可能用于如下幾種情況:
(1) 并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器,例中的代碼屬于此類);
(2) 一個(gè)中斷服務(wù)子程序中會訪問到的非自動變量(也就是全局變量);
(3) 多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量。
CPU字長與存儲器位寬不一致處理
在背景篇中提到,本文特意選擇了一個(gè)與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中寫一個(gè)字節(jié)
*參數(shù):wOffset,寫入位置相對NVRAM基地址的偏移
* byData,欲寫入的字節(jié)
*/
extern void WriteByteNVRAM(WORD wOffset, BYTE byData)
{
…
}
/* 函數(shù)功能:向NVRAM中寫一個(gè)字 */
*參數(shù):wOffset,寫入位置相對NVRAM基地址的偏移
* wData,欲寫入的字
*/
extern void WriteWordNVRAM(WORD wOffset, WORD wData)
{
…
}
子貢問曰:Why偏移要乘以2?
子曰:請看圖1,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ì)算機(jī)組成的圣人之道。
總結(jié)
本篇主要講述了嵌入式系統(tǒng)C編程中內(nèi)存操作的相關(guān)技巧。掌握并深入理解關(guān)于數(shù)據(jù)指針、函數(shù)指針、動態(tài)申請內(nèi)存、const及volatile關(guān)鍵字等的相關(guān)知識,是一個(gè)優(yōu)秀的C語言程序設(shè)計(jì)師的基本要求。當(dāng)我們已經(jīng)牢固掌握了上述技巧后,我們就已經(jīng)學(xué)會了C語言的99%,因?yàn)镃語言最精華的內(nèi)涵皆在內(nèi)存操作中體現(xiàn)。
我們之所以在嵌入式系統(tǒng)中使用C語言進(jìn)行程序設(shè)計(jì),99%是因?yàn)槠鋸?qiáng)大的內(nèi)存操作能力!
如果你愛編程,請你愛C語言;
如果你愛C語言,請你愛指針;
如果你愛指針,請你愛指針的指針!
C語言嵌入式系統(tǒng)編程注意事項(xiàng)之屏幕操作
現(xiàn)在要解決的問題是,嵌入式系統(tǒng)中經(jīng)常要使用的并非是完整的漢字庫,往往只是需要提供數(shù)量有限的漢字供必要的顯示功能
漢字處理
現(xiàn)在要解決的問題是,嵌入式系統(tǒng)中經(jīng)常要使用的并非是完整的漢字庫,往往只是需要提供數(shù)量有限的漢字供必要的顯示功能。例如,一個(gè)微波爐的LCD上沒有必要提供顯示“電子郵件”的功能;一個(gè)提供漢字顯示功能的空調(diào)的LCD上不需要顯示一條“短消息”,諸如此類。但是一部手機(jī)、小靈通則通常需要包括較完整的漢字庫。
如果包括的漢字庫較完整,那么,由內(nèi)碼計(jì)算出漢字字模在庫中的偏移是十分簡單的:漢字庫是按照區(qū)位的順序排列的,前一個(gè)字節(jié)為該漢字的區(qū)號,后一個(gè)字節(jié)為該字的位號。每一個(gè)區(qū)記錄94個(gè)漢字,位號則為該字在該區(qū)中的位置。因此,漢字在漢字庫中的具體位置計(jì)算公式為:94*(區(qū)號-1)+位號-1。減1是因?yàn)閿?shù)組是以0為開始而區(qū)號位號是以1為開始的。只需乘上一個(gè)漢字字模占用的字節(jié)數(shù)即可,即:(94*(區(qū)號-1)+位號-1)*一個(gè)漢字字模占用字節(jié)數(shù),以16*16點(diǎn)陣字庫為例,計(jì)算公式則為:(94*(區(qū)號-1)+(位號-1))*32。漢字庫中從該位置起的32字節(jié)信息記錄了該字的字模信息。
對于包含較完整漢字庫的系統(tǒng)而言,我們可以以上述規(guī)則計(jì)算字模的位置。但是如果僅僅是提供少量漢字呢?譬如幾十至幾百個(gè)?最好的做法是:
定義宏:
# define EX_FONT_CHAR()
# define EX_FONT_UNICODE_VAL() (),
# define EX_FONT_ANSI_VAL() (),
定義結(jié)構(gòu)體:
typedef struct _wide_unicode_font16x16
{
WORD ; /* 內(nèi)碼 */
BYTE data[32]; /* 字模點(diǎn)陣 */
}Unicode;
#define CHINESE_CHAR_NUM … /* 漢字?jǐn)?shù)量 */
評論
查看更多