1. 程式人生 > >嵌入式程式設計中關於const,static,extern,volatile的用法

嵌入式程式設計中關於const,static,extern,volatile的用法

一.const的用法:為什麼使用const? 採用符號常量寫出的程式碼更容易維護;指標常常是邊讀邊移動,而不是邊寫邊移動;許多函式引數是隻讀不寫的。const最常見用途是作為陣列的邊界和switch分支標號(也可以用列舉符代替)
用法1:常量    取代了C中的巨集定義,宣告時必須進行初始化。const限制了常量的使用方式,並沒有描述常量應該如何分配。如果編譯器知道了某const的所有使用,它甚至可以不為該const分配空間。最簡單的常見情況就是常量的值在編譯時已知,而且不需要分配儲存。―《C++ Program Language》    用const宣告的變數雖然增加了分配空間,但是可以保證型別安全。C標準中,const定義的常量是全域性的,C++中視宣告位置而定。
用法2:指標和常量
   使用指標時涉及到兩個物件:該指標本身和被它所指的物件。將一個指標的宣告用const“預先固定”將使那個物件而不是使這個指標成為常量。要將指標本身而不是被指物件宣告為常量,必須使用宣告運算子*const所以出現在 * 之前的const是作為基礎型別的一部分: char *const cp; //到char的const指標

(後兩個宣告是等同的)

char const *pc1; //到const char的指標 const char *pc2; //到const char的指標 從右向左讀的記憶方式: cp is a const pointer to char. pc2 is a pointer to const char.
用法3:const修飾函式傳入引數
    將函式傳入引數宣告為const,以指明使用這種引數僅僅是為了效率的原因,而不是想讓呼叫函式能夠修改物件的值。同理,將指標引數宣告為const,函式將不修改由這個引數所指的物件。     通常修飾指標引數和引用引數: void Fun(const A *in); //修飾指標型傳入引數 void Fun(const A &in); //修飾引用型傳入引數

//注意引用的概念,我得C學得不夠好!icon

用法4:修飾函式返回值    可以阻止使用者修改返回值。返回值也要相應的付給一個常量或常指標。 用法5:const修飾成員函式

//這個地方也理解不好啊!icon

const物件只能訪問const成員函式,而非const物件可以訪問任意的成員函式,包括const成員函式;
const物件的成員是不能修改的,而通過指標維護的物件確實可以修改的; const成員函式不可以修改物件的資料,不管物件是否具有const性質。編譯時以是否修改成員資料為依據進行檢查。

      2.static的用法: 全域性靜態變數怎麼用?? 所謂的全域性靜態變數只在當前程式中有效。比如在a.c中定義了 static int i; 則只有a.c中的函式可以訪問i,其它程式模組的無法直接訪問這個變數,但可以通過a.c中所定義的函式間接訪問。比如在a.c中定義兩個函式: int get_i() { return i; }; int set_i( int n ) { i=n; } 以此獲得及重新設定i的值。這在c環境下是一種較好的模擬C++風格的實現方法。因為你可以將a.c看作一個類,i看作a的成員,而get_i()和set_i()則看作這個類的成員函式。 靜態變數作用範圍在一個檔案內,程式開始時分配空間,結束時釋放空間,預設初始化為0,使用時可以改變其值。 靜態變數或靜態函式只有本檔案內的程式碼才能訪問它,它的名字在其它檔案中不可見。 用法1:函式內部宣告的static變數,可作為物件間的一種通訊機制 如果一區域性變數被宣告為static,那麼將只有唯一的一個靜態分配的物件,它被用於在該函式的所有呼叫中表示這個變數。這個物件將只在執行執行緒第一次到達它的定義時初始化。 用法2:區域性靜態物件 對於區域性靜態物件,建構函式是在控制執行緒第一次通過該物件的定義時呼叫。在程式結束時,區域性靜態物件的解構函式將按照他們被構造的相反順序逐一呼叫,沒有規定確切時間。 用法3:靜態成員和靜態成員函式 如果一個變數是類的一部分,但卻不是該類的各個物件的一部分,它就被成為是一個static靜態成員。一個static成員只有唯一的一份副本,而不像常規的非static成員那樣在每個物件裡各有一份副本。同理,一個需要訪問類成員,而不需要針對特定物件去呼叫的函式,也被稱為一個static成員函式。 類的靜態成員函式只能訪問類的靜態成員(變數或函式)。
3.extern的用法: extern可以宣告其他檔案內定義的變數。在一個程式裡,一個物件只能定義一次,它可以有多個宣告,但型別必須完全一樣。如果定義在全域性作用域或者名字空間作用域裡某一個變數沒有初始化,它會被按照預設方式初始化。 將變數或函式宣告成外部連結,即該變數或函式名在其它函式中可見。被其修飾的變數(外部變數)是靜態分配空間的,即程式開始時分配,結束時釋放。 在C++中,還可以指定使用另一語言連結,需要與特定的轉換符一起使用。 extern “C” 宣告語句 extern “C” { 宣告語句塊 } 4.volatile的用法:      型別修正符(type-modifier),限定一個物件可被外部程序(作業系統、硬體或併發程序等)改變。volatile與變數連用,可以讓變數被不同的執行緒訪問和修改。宣告時語法:int volatile vInt;     除了基本型別外,對使用者定義型別也可以用volatile型別進行修飾。 注意:可以把一個非volatile int賦給volatile int,但是不能把非volatile物件賦給一個volatile物件。 一個有volatile識別符號的類只能訪問它介面的子集,一個由類的實現者控制的子集。使用者只能用const_cast來獲得對型別介面的完全訪問。此外,volatile向const一樣會從類傳遞到它的成員。

       volatile的本意是“易變的”

       由於訪問暫存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優化。比如:

bit bFlag="0"; int main(void) { ... while (1) { if (bFlag) dosomething(); } } /* 中斷程式*/ void ISR(void) { bFlag=1; } 程式的本意是希望ISR中斷產生時,在main當中呼叫dosomething函式,但是,由於編譯器判 斷在main函式裡面沒有修改過bFlag,因此 可能只執行一次對從bFlag到某暫存器的讀操作,然後每次if判斷都只使用這個暫存器裡面 的“bFlag副本”,導致dosomething永遠也不會被呼叫。如果將將變數加上volatile修飾, 則編譯器保證對此變數的讀寫操作都不會被優化(肯定執行)。此例中bFlag也應該如此說 明。

        volatile是一個限定符,也稱為keyword或描述符,"volatile 關鍵字指示欄位可由作業系統、硬體或併發執行的執行緒在程式中進行修改。"

當要求使用volatile 宣告的變數的值的時候,系統總是重新從它所在的記憶體讀取資料,即使它前面的指令剛剛從該處讀取過資料。而且讀取的資料立刻被儲存。

一般說來,volatile用在如下的幾個地方:

1、中斷服務程式中修改的供其它程式檢測的變數需要加volatile;

2、多工環境下各任務間共享的標誌應該加volatile;

3、儲存器對映的硬體暫存器通常也要加volatile說明,因為每次對它的讀寫都可能由不同意義;

備註 系統總是在 volatile 物件被請求的那一刻讀取其當前值,即使上一條指令從同一物件請求值。而且,該物件的值在賦值時立即寫入。

volatile 修飾符通常用於由多個執行緒訪問而不使用 lock 語句來序列化訪問的欄位。使用 volatile 修飾符能夠確保一個執行緒檢索由另一執行緒寫入的最新值。

      另外,以上這幾種情況經常還要同時考慮資料的完整性(相互關聯的幾個標誌讀了一半被打斷了重寫),在1中可以通過關中斷來實現,2中可以禁止任務排程,3中則只能依靠硬體的良好設計了。

     volatile 的含義      volatile總是與優化有關,編譯器有一種技術叫做資料流分析,分析程式中的變數在哪裡賦值、在哪裡使用、在哪裡失效,分析結果可以用於常量合併,常量傳播等優化,進一步可以死程式碼消除。但有時這些優化不是程式所需要的,這時可以用volatile關鍵字禁止做這些優化,volatile的字面含義是易變的,它有下面的作用:

     1 不會在兩個操作之間把volatile變數快取在暫存器中。在多工、中斷、甚至setjmp環境下,變數可能被其他的程式改變,編譯器 自己無法知道,volatile就是告訴編譯器這種情況。

     2 不做常量合併、常量傳播等優化,所以像下面的程式碼:          volatile int i = 1;          if (i > 0) ...

        if的條件不會當作無條件真。

     3 對volatile變數的讀寫不會被優化掉。如果你對一個變數賦值但後面沒用到,編譯器常常可以省略那個賦值操作,然而對Memory Mapped IO的處理是不能這樣優化的。

     一個網友說:volatile的意思是什麼? 很多時候,全域性變數不一定是全域性的,在多執行緒環境下可能產生微妙的錯誤,很有可能編譯器為了優化,而把一個全域性變數放入暫存器裡。volatile修飾符就是明確告訴編譯器,你他媽不準把這個變數優化到暫存器上,只能放記憶體裡。

-----------------------------------------------------------------------------------------------------------------------------

const應用: 一、對於基本宣告     const int r=100;//標準const變數宣告加初始化,編譯器經過型別檢查後直接用100在編譯時替換。  二、對於指標     1. int x=10; const int *r=&x; //指標指向的內容是常量,r指向的內容不能夠通過r改變,但如果是非const,內容可以通過自己改變,而且r指標可以改變,可以指向其它的整形.     //*r=*r+1;NO //x++;YES //r=&y;YES     2. int const *r=&x; 與1完全相同     3. int * const r=&x; //指標指向是常量,不能修改去指向其它內容,但指向的內容可以修改      //r=&y;NO //*r=*r+1;YES //x++;YES      4.const int * const r=&x; //綜合1、3用法,r是一個指向常量的常量型指標,指標指向不能改變,指標內容不能改變,內容可以自身改變

    //r=&y;NO //*r=*r+1;NO //x++;YES

三、對於型別檢查     可以把非const物件賦予const指標,這樣就不能改變.但是不能把const賦給非const,除非先強制轉換 const int x=100; int *p=(int*)&x; *p++; 四、對於函式     1.void Fuction1(const int r); //此處為引數傳遞const值,意義是變數初值不能被函式改變     2.const int Fuction1 (int); //此處返回const值,意思指返回的原函式裡的變數的初值不能被修改,但是函式按值返回的這個變數被製成副本,能不能被修改就沒有了意義,它可以被賦給任何的const或非const型別變數,完全不需要加上這個const關鍵字。     3.Class CX; //內部有建構函式,宣告如CX(int r =0)       CX Fuction1 () { return CX(); }       const CX Fuction2 () { return CX(); }       Fuction1() = CX(1); //沒有問題,可以作為左值呼叫       Fuction2() = CX(1); //編譯錯誤,const返回值禁止作為左值呼叫。     4.函式中指標的const傳遞和返回:     int F1 (const char *pstr); //作為傳遞的時候使用const修飾可以保證不會通過這個指標來修改傳遞引數的初值     const char *F2();//意義是函式返回的指標指向的物件是一個const物件,它必須賦給一個同樣是指向const物件的指標     const char * const F3(); //比上面多了一個const,這個const的意義只是在他被用作左值時有效,它表明了這個指標除了指向const物件外,它本身也不能被修改,所以就不能當作左值來處理。 五、對於類     1.首先,對於const的成員變數,只能在建構函式裡使用初始化成員列表來初始化,試圖在建構函式體內進行初始化const成員變數會引起編譯錯誤。初始化成員列表形如:    X:: X ( int ir ): r(ir) {} //假設r是類X的const成員變數       注意:類的構造和解構函式都不能是const函式。     2.建立了一個const成員函式,但仍然想用這個函式改變物件內部的資料。(函式不能修改類的資料成員) //假如有一個叫做X的類,它有一個int成員變數r,我們需要通過一個const成員函式f( )來對這個r進行++r操作,程式碼如下 void X::f( ) const { (const_cast(this)) -> ++r; } //通過this指標進行型別強制轉換實現 --------------------------------STATIC-------------------------------- 對於一個完整的程式,記憶體中的分佈情況:       ==========       |      程式碼區    |       ------------------       | 全域性資料區 |       ------------------       |       堆區       |       -----------------       |        棧區      |       ==========     一般程式的由new產生的動態資料存放在堆區,函式內部的自動變數存放在棧區,全域性變數和static變數放在全域性資料區 static的作用主要有以下3個:     1、擴充套件生存期;     2、限制作用域;     3、唯一性

STATIC:

一、面向過程設計中的static     1、[靜態全域性變數] //在全域性變數前,加上關鍵字static,該變數就被定義成為一個靜態全域性變數。      靜態全域性變數有以下特點:          1)該變數在全域性資料區分配記憶體;         2)未經初始化的靜態全域性變數會被程式自動初始化為0(自動變數的值是隨機的,除非它被顯式初始化);          3)靜態全域性變數在宣告它的整個檔案都是可見的,而在檔案之外(extern)是不可見的;   定義全域性變數就可以實現變數在檔案中的共享,但定義靜態全域性變數還有以下好處:         1)靜態全域性變數不能被其它檔案所用;         2)其它檔案中可以定義相同名字的變數,不會發生衝突;

    2、[靜態區域性變數] 在區域性變數前,加上關鍵字static,該變數就被定義成為一個靜態區域性變數。

    通常,在函式體內定義了一個變數,每當程式執行到該語句時都會給該區域性變數分配棧記憶體。但隨著程式退出函式體,系統就會收回棧記憶體,區域性變數也相應失效。但有時候我們需要在兩次呼叫之間對變數的值進行儲存。通常的想法是定義一個全域性變數來實現。但這樣一來,變數已經不再屬於函式本身了,不再僅受函式的控制,給程式的維護帶來不便。 靜態區域性變數正好可以解決這個問題。靜態區域性變數儲存在全域性資料區,而不是儲存在棧中,每次的值保持到下一次呼叫,直到下次賦新值。     靜態區域性變數有以下特點:       1)該變數在全域性資料區分配記憶體;        2)靜態區域性變數在程式執行到該物件的宣告處時被首次初始化,即以後的函式呼叫不再進行初始化;        3)靜態區域性變數一般在宣告處初始化,如果沒有顯式初始化,會被程式自動初始化為0;        4)它始終駐留在全域性資料區,直到程式執行結束。但其作用域為區域性作用域,當定義它的函式或語句塊結束時,其作用域隨之結束;     3、靜態函式     在函式的返回型別前加上static關鍵字,函式即被定義為靜態函式。靜態函式與普通函式不同,它只能在宣告它的檔案當中可見,不能被其它檔案使用。    定義靜態函式的好處:        1)靜態函式不能被其它檔案所用;        2)其它檔案中可以定義相同名字的函式,不會發生衝突; 二、面向物件的static關鍵字(類中的static關鍵字) 1、靜態資料成員     在類內資料成員的宣告前加上關鍵字static,該資料成員就是類內的靜態資料成員。    靜態資料成員有以下特點:       1)而靜態資料成員被當作是類的成員。無論這個類的物件被定義了多少個,靜態數 據成員在程式中也只有一份拷貝,由該型別的所有物件共享訪問。       2)靜態資料成員儲存在全域性資料區,屬於本類的所有物件共享,所以,它不屬於特定的類物件,在沒有產生類物件時其作用域就可見,即在沒有產生類的例項時,我們就可以操作它;  同全域性變數相比,使用靜態資料成員有兩個優勢:       1)靜態資料成員沒有進入程式的全域性名字空間,因此不存在與程式中其它全域性名字衝突的可能性;      2)可以實現[資訊隱藏]。靜態資料成員可以是private成員,而全域性變數不能; 2、靜態成員函式     它為類的全部服務而不是為某一個類的具體物件服務。與普通函式相比,靜態成員函式由於不是與任何的 物件相聯絡,因此它不具有this指標。從這個意義上講,它無法訪問屬於類物件的非靜態資料成員,也無法訪問非靜態成員函式,它只能呼叫其餘的靜態成員函式。 關於靜態成員函式,可以總結為以下幾點:      1)出現在類體外的函式定義不能指定關鍵字static;      2)靜態成員之間可以相互訪問,包括靜態成員函式訪問靜態資料成員和訪問靜態成員函式;      3)非靜態成員函式可以任意地訪問靜態成員函式和靜態資料成員;      4)靜態成員函式不能訪問非靜態成員函式和非靜態資料成員 -----------------------------------EXTERN---------------------------- EXTERN 1 基本解釋     extern可以置於變數或者函式前,以標示變數或者函式的定義在別的檔案中,提示編譯器遇到此變數和函式時在其他模組中尋找其定義。通過這種行為它告訴編譯器:該變數/函式的定義已經存在在某個地方了,讓編譯器到其他的模組去尋找它的定義。     另外,extern也可用來進行連結指定。 2. extern   “C”    使用extern“C”主要是因為C++語言在編譯的時候為了實現多型,會將函式名和函式結合起來形成另外一種函式名(總之就是說編譯後的函式名與你之前自己宣告時的函式名會不一樣),而C語言中無多型的概念當然也就不會有這種奇異的名字變化問題。這是問題就出現了,當你要在C++中呼叫C函式時,由於名字的不同,所以它會找不到所呼叫的這個函式的定義,因而會出錯。    為了解決這一C與C++的矛盾衝突,就有了extern "C'。