C語言嵌入式系統程式設計修煉之記憶體操作

2021-04-01 04:48:08 字數 4650 閱讀 6396

資料指標

在嵌入式系統的程式設計中,常常要求在特定的記憶體單元讀寫內容,彙編有對應的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。

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暫存器;

請拿出你可以獲得的任何一本大學《微型計算機原理》教材,書中講到,186 cpu啟動後跳轉至絕對位址0xffff0(對應c語言指標是0xf000fff0,0xf000為段位址,0xfff0為段內偏移)執行,請看下面的**:

typedef void (*lpfunction) ( ); /* 定義乙個無引數、無返回型別的 */

/* 函式指標型別 */

lpfunction lpreset = (lpfunction)0xf000fff0; /* 定義乙個函式指標,指向*/

/* cpu啟動後所執行第一條指令的位置 */

lpreset(); /* 呼叫函式 */

在以上的程式中,我們根本沒有看到任何乙個函式實體,但是我們卻執行了這樣的函式呼叫:lpreset(),它實際上起到了"軟重啟"的作用,跳轉到cpu啟動後第一條要執行的指令的位置。

陣列vs.動態申請

在嵌入式系統中動態記憶體申請存在比一般系統程式設計時更嚴格的要求,這是因為嵌入式系統的記憶體空間往往是十分有限的,不經意的記憶體洩露會很快導致系統的崩潰。

所以一定要保證你的malloc和free成對出現,如果你寫出這樣的一段程式:

char * function(void)

在某處呼叫function(),用完function中動態申請的記憶體後將其free,如下:

char *q = function();

…free(q);

上述**明顯是不合理的,因為違反了malloc和free成對出現的原則,即"誰申請,就由誰釋放"原則。不滿足這個原則,會導致**的耦合度增大,因為使用者在呼叫function函式時需要知道其內部細節!

正確的做法是在呼叫處申請記憶體,並傳入function函式,如下:

char *p=malloc(…);

if(p==null)

…;function(p);

…free(p);

p=null;

而函式function則接收引數p,如下:

void function(char *p)

基本上,動態申請記憶體方式可以用較大的陣列替換。對於程式設計新手,筆者推薦你盡量採用陣列!嵌入式系統可以以博大的胸襟接收瑕疵,而無法"海納"錯誤。畢竟,以最笨的方式苦練神功的郭靖勝過機智聰明卻範政治錯誤走反革命道路的楊康。

給出原則:

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

(2)如果使用動態申請,則申請後一定要判斷是否申請成功了,並且malloc和free應成對出現!

關鍵字const

const意味著"唯讀"。區別如下**的功能非常重要,也是老生長嘆,如果你還不知道它們的區別,而且已經在程式界摸爬滾打多年,那只能說這是乙個悲哀:

const int a;

int const a;

const int *a;

int * const a;

int const * a const;

(1) 關鍵字const的作用是為給讀你**的人傳達非常有用的資訊。例如,在函式的形參前新增const關鍵字意味著這個引數在函式體內不會被修改,屬於"輸入引數"。在有多個形參的時候,函式的呼叫者可以憑藉引數前是否有const關鍵字,清晰的辨別哪些是輸入引數,哪些是可能的輸出引數。

(2)合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的引數,防止其被無意的**修改,這樣可以減少bug的出現。

const在c++語言中則包含了更豐富的含義,而在c語言中僅意味著:"只能讀的普通變數",可以稱其為"不能改變的變數"(這個說法似乎很拗口,但卻最準確的表達了c語言中const的本質),在編譯階段需要的常數仍然只能以#define巨集定義!故在c語言中如下程式是非法的:

const int size = 10;

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

關鍵字volatile

c語言編譯器會對使用者書寫的**進行優化,譬如如下**:

int a,b,c;

a = inword(0x100); /*讀取i/o空間0x100埠的內容存入a變數*/

b = a;

a = inword (0x100); /*再次讀取i/o空間0x100埠的內容存入a變數*/

c = a;

很可能被編譯器優化為:

int a,b,c;

a = inword(0x100); /*讀取i/o空間0x100埠的內容存入a變數*/

b = a;

c = a;

但是這樣的優化結果可能導致錯誤,如果i/o空間0x100埠的內容在執行第一次讀操作後被其它程式寫入新值,則其實第2次讀操作讀出的內容與第一次不同,b和c的值應該不同。在變數a的定義前加上volatile關鍵字可以防止編譯器的類似優化,正確的做法是:

volatile int a;

volatile變數可能用於如下幾種情況:

(1) 並行裝置的硬體暫存器(如:狀態暫存器,例中的**屬於此類);

(2) 乙個中斷服務子程式中會訪問到的非自動變數(也就是全域性變數);

(3) 多執行緒應用中被幾個任務共享的變數。

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

在背景篇中提到,本文特意選擇了乙個與cpu字長不一致的儲存晶元,就是為了進行本節的討論,解決cpu字長與儲存器位寬不一致的情況。80186的字長為16,而nvram的位寬為8,在這種情況下,我們需要為nvram提供讀寫位元組、字的介面,如下:

typedef unsigned char byte;

typedef unsigned int word;

/* 函式功能:讀nvram中位元組

* 引數:woffset,讀取位置相對nvram基位址的偏移

* 返回:讀取到的位元組值

*/extern byte readbytenvram(word woffset)

/* 函式功能:讀nvram中字

* 引數:woffset,讀取位置相對nvram基位址的偏移

* 返回:讀取到的字

*/extern word readwordnvram(word woffset)

/* 函式功能:向nvram中寫乙個位元組

*引數:woffset,寫入位置相對nvram基位址的偏移

* bydata,欲寫入的位元組

*/extern void writebytenvram(word woffset, byte bydata)

/* 函式功能:向nvram中寫乙個字 */

*引數:woffset,寫入位置相對nvram基位址的偏移

* wdata,欲寫入的字

*/extern void writewordnvram(word woffset, word wdata)

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

圖1 cpu與nvram位址線連線

子曰:請看《it論語》之《微機原理篇》,那裡面講述了關於計算機組成的聖人之道。

總結

本篇主要講述了嵌入式系統c程式設計中記憶體操作的相關技巧。掌握並深入理解關於資料指標、函式指標、動態申請記憶體、const及volatile關鍵字等的相關知識,是乙個優秀的c語言程式設計師的基本要求。當我們已經牢固掌握了上述技巧後,我們就已經學會了c語言的99%,因為c語言最精華的內涵皆在記憶體操作中體現。

我們之所以在嵌入式系統中使用c語言進行程式設計,99%是因為其強大的記憶體操作能力!

如果你愛程式設計,請你愛c語言;

如果你愛c語言,請你愛指標;

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

C語言嵌入式系統程式設計修煉之記憶體操作

資料指標 在嵌入式系統的程式設計中,常常要求在特定的記憶體單元讀寫內容,彙編有對應的mov指令,而除c c 以外的其它程式語言基本沒有直接訪問絕對位址的能力。在嵌入式系統的實際除錯中,多借助c語言指標所具有的對絕對位址單元內容的讀寫能力。以指標直接操作記憶體多發生在如下幾種情況 1 某i o晶元被定...

C語言嵌入式系統程式設計修煉之記憶體操作

資料指標 在嵌入式系統的程式設計中,常常要求在特定的 記憶體單元讀寫內容,彙編有對應的mov指令,而除 c c 以外的其它程式語言基本沒有直接訪問絕對位址的能力。在嵌入式系統的實際除錯中,多借助c語言指標所具有的對絕對位址單元內容的讀寫能力。以指標直接操作記憶體多發生在如下幾種情況 1 某i o晶元...

C語言嵌入式系統程式設計修煉之記憶體操作 1

資料指標 在嵌入式系統的程式設計中,常常要求在特定的記憶體單元讀寫內容,彙編有對應的mov指令,而除c c 以外的其它程式語言基本沒有直接訪問絕對位址的能力。在嵌入式系統的實際除錯中,多借助c語言指標所具有的對絕對位址單元內容的讀寫能力。以指標直接操作記憶體多發生在如下幾種情況 1 某i o晶元被定...