天天看點

修飾代碼的關鍵字

volatile
           

volatile的目的是,避免進行預設的優化處理.比如說對于編譯器優化的功能,如果從編譯器看來,有些多餘的代碼的話,編譯器就會啟動優化程式,并删除一些代碼,但是這在嵌入式系統中很有可能是關鍵性的處理,必須不能保證被編譯器删掉,是以提供了Volitile來聲明,告訴編譯器無論如何都不要删掉我。舉個例子–■比如說下面條件的一段代碼externintevent_flagvoidpoll_event(){

while(event_flag0){

/不操作event_flag/…}…}

我們不再循環中改變這裡的event_flag的值,這樣的話,event_flag看起來就像是多餘的,是以單片機編譯器可能把此程式看為下段程式voidpoll_event(){

if(event_flag0){

while(1){

/不對event_flag操作/…}}…}

對于一般的編譯器,一般都會把程式優化成上述程式。

這樣的優化确實可以提高代碼速度,比如while循環中不再需要對條件的判斷,是以很快,但是這是正确的嗎?

對于單線程的程式,這是沒有問題的,因為event_flag就永遠不會改變,但是對于多線程程式,RTOS的多任務處理的話,event_flag的值可能被其他線程改變,這樣問題就來了,因為被優化的代碼并不具備對用event_flag變化的能力。是以導緻錯誤的意想不到的結果,如果此代碼在ECU上執行的話,那我們的小命可就有可能沒了。。。。為了避免這種情況,我們使用volatile關鍵字來防止程式被編譯器優化。具體的使用方法,我們用下面的程式來說明’externvolatileintevent_flag

這樣聲明event_flag全局變量的話,就不用擔心event_flag被優化掉,程式将按照設計來運作。

■還有一個例子

對于條件分歧以外,還有一下的例子externintp_regster1;externintp_regster2;

voidset_regester2(intval){

/在單片機中,必須進行的設定/*p_register1=1;*p_register2=0;*p_register2=val;*p_register1=0;}

您可能看到p_register1被指派兩次,還有p_register2也是,編譯器認為,你怎麼這麼笨,定義兩次,于是就把成程式優化為下面

voidset_regester2(intval){

*p_register2=val;p_register1=0;}

這樣的話,我們所規定的程式沒有辦法設定,可能導緻一些想不到的問題。為了回避這個問題,我們必須用Volitile來避免這個問題externvolitileintp_regster1;

externvolitileint*p_regster2;

現在單片機的編譯器越來越先進,在很多地方,我們不再需要直接寫彙編代碼,但是在如果對編譯器的優化程式沒有深刻的了解,像上面的問題,就很危險,因為嵌入式工作在無人的環境中,是以對于編譯器的了解,還有要需要一定程式的學習。

register
           

register:這個關鍵字請求編譯器盡可能的将變量存在CPU内部寄存器中,而不是通過記憶體尋址通路,以提高效率。注意是盡可能,不是絕對。

因為,如果定義了很多register變量,可能會超過CPU的寄存器個數,超過容量。是以隻是可能。

不知道什麼是寄存器?那見過太監沒有?沒有?其實我也沒有。沒見過不要緊,見過就麻煩大了。_,大家都看過古裝戲,那些皇帝們要閱讀奏章的時候,大臣總是先将奏章交給皇帝旁邊的小太監,小太監呢再交給皇帝同志處理。這個小太監隻是個中轉站,并無别的功能。

那我們再聯想到我們的CPU。CPU 不就是我們的皇帝同志麼?大臣就相當于我們的記憶體,資料從他這拿出來。那小太監就是我們的寄存器了(這裡先不考慮CPU 的高速緩存區)。資料從記憶體裡拿出來先放到寄存器,然後CPU 再從寄存器裡讀取資料來處理,處理完後同樣把資料通過寄存器存放到記憶體裡,CPU 不直接和記憶體打交道。這裡要說明的一點是:小太監是主動的從大臣手裡接過奏章,然後主動的交給皇帝同志,但寄存器沒這麼自覺,它從不主動幹什麼事。一個皇帝可能有好些小太監,那麼一個CPU 也可以有很多寄存器,不同型号的CPU 擁有寄存器的數量不一樣。

為啥要這麼麻煩啊?速度!就是因為速度。寄存器其實就是一塊一塊小的存儲空間,隻不過其存取速度要比記憶體快得多。進水樓台先得月嘛,它離CPU 很近,CPU 一伸手就拿到資料了,比在那麼大的一塊記憶體裡去尋找某個位址上的資料是不是快多了?

使用register修飾符有幾點限制。
           

1.register變量必須是能被CPU所接受的類型。這通常意味着register變量必須是一個單個的值,并且長度應該小于或者等于整型的長度。不過,有些機器的寄存器也能存放浮點數。

2.因為register變量可能不存放在記憶體中,是以不能用“&”來擷取register變量的位址。由于寄存器的數量有限,而且某些寄存器隻能接受特定類型的資料(如指針和浮點數),是以真正起作用的register修飾符的數目和類型都依賴于運作程式的機器,而任何多餘的register修飾符都将被編譯程式所忽略。在某些情況下,把變量儲存在寄存器中反而會降低程式的運作速度。因為被占用的寄存器不能再用于其它目的;或者變量被使用的次數不夠多,不足以裝入和存儲變量所帶來的額外開銷。

3.早期的C編譯程式不會把變量儲存在寄存器中,除非你指令它這樣做,這時register修飾符是C語言的一種很有價值的補充。然而,随着編譯程式設計技術的進步,在決定那些變量應該被存到寄存器中時,現在的C編譯環境能比程式員做出更好的決定。實際上,許多編譯程式都會忽略register修飾符,因為盡管它完全合法,但它僅僅是暗示而不是指令。

static關鍵字在c語言中比較常用,使用恰當能夠大大提高程式的子產品化特性,有利于擴充和維護。
           

在程式中使用static

變量

  1. 局部變量

    普通局部變量是再熟悉不過的變量了,在任何一個函數内部定義的變量(不加static修飾符)都屬于這個範疇。編譯器一般不對普通局部變量進行初始化,也就是說它的值在初始時是不确定的,除非對其顯式指派。

普通局部變量存儲于程序棧空間,使用完畢會立即釋放。

靜态局部變量使用static修飾符定義,即使在聲明時未賦初值,編譯器也會把它初始化為0。且靜态局部變量存儲于程序的全局資料區,即使函數傳回,它的值也會保持不變。

變量在全局資料區配置設定記憶體空間;編譯器自動對其初始化

其作用域為局部作用域,當定義它的函數結束時,其作用域随之結束

  1. 全局變量

    全局變量定義在函數體外部,在全局資料區配置設定存儲空間,且編譯器會自動對其初始化。

普通全局變量對整個工程可見,其他檔案可以使用extern外部聲明後直接使用。也就是說其他檔案不能再定義一個與其相同名字的變量了(否則編譯器會認為它們是同一個變量)。

靜态全局變量僅對目前檔案可見,其他檔案不可通路,其他檔案可以定義與其同名的變量,兩者互不影響。

函數
           

函數的使用方式與全局變量類似,在函數的傳回類型前加上static,就是靜态函數。其特性如下:

靜态函數隻能在聲明它的檔案中可見,其他檔案不能引用該函數

不同的檔案可以使用相同名字的靜态函數,互不影響

非靜态函數可以在另一個檔案中直接引用,甚至不必使用extern聲明

extern
           

關鍵字extern,可以在一個檔案中引用另一個檔案中定義的變量或者函數

extern可以置于變量或者函數前,以标示變量或者函數的定義在别的檔案中,提示編譯器遇到此變量和函數時在其他子產品中尋找其定義

void
           

void類型修飾符(type specifier)表示“沒有值可以獲得”。是以,不可以采用這個類型聲明變量或常量。

void用于函數聲明

沒有傳回值的函數,其類型為 void

參數清單中的關鍵字 void 表示該函數沒有參數

指向void的指針

一個 void* 類型的指針代表了對象的位址,但沒有該對象的類型資訊。這種“無資料類型”的指針主要用于聲明函數,讓函數可使用各種類型的指針參數,或者傳回一個“多用途”的指針

1)C 語言規定隻有相同類型的指針才可以互相指派

(2)void* 指針作為左值用于“接收”任意類型的指針

(3)void* 指針作為右值使用時需要進行強制類型轉換