1.static關(guān)鍵字
這個關(guān)鍵字前面也有提到,它的作用是強大的。
要對static關(guān)鍵字深入了解,首先需要掌握標準C程序的組成。
標準C程序一直由下列部分組成:
1)正文段——CPU執(zhí)行的機器指令部分,也就是你的程序。一個程序只有一個副本;只讀,這是為了防止程序由于意外事故而修改自身指令; 2)初始化數(shù)據(jù)段(數(shù)據(jù)段)——在程序中所有賦了初值的全局變量,存放在這里。 3)非初始化數(shù)據(jù)段(bss段)——在程序中沒有初始化的全局變量;內(nèi)核將此段初始化為0。
注意:只有全局變量被分配到數(shù)據(jù)段中。 4)?!鲩L方向:自頂向下增長;自動變量以及每次函數(shù)調(diào)用時所需要保存的信息(返回地址;環(huán)境信息)。這句很關(guān)鍵,常常有筆試題會問到什么東西放到棧里面就足以說明。 5)堆——動態(tài)存儲分配。
作用一:在函數(shù)體,一個被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過程中維持其值不變。
這樣定義的變量稱為局部靜態(tài)變量:在局部變量之前加上關(guān)鍵字static,局部變量就被定義成為一個局部靜態(tài)變量。也就是上面的作用一中提到的在函數(shù)體內(nèi)定義的變量。除了類型符外,若不加其它關(guān)鍵字修飾,默認都是局部變量。比如以下代碼:
void test1(void)
{
unsigned char a;
static unsigned char b;
…
a++;
b++;
}
在這個例子中,變量a是局部變量,變量b為局部靜態(tài)變量。作用一說明了局部靜態(tài)變量b的特性:在函數(shù)體,一個被聲明為靜態(tài)的變量(也就是局部靜態(tài)變量)在這一函數(shù)被調(diào)用過程中維持其值不變。這句話什么意思呢?若是連續(xù)兩次調(diào)用上面的函數(shù)test1:
void main(void)
{
…
test1();
test1();
…
}
然后使程序暫停下來,讀取a和b的值,你會發(fā)現(xiàn),a=1,b=2。怎么回事呢,每次調(diào)用test1函數(shù),局部變量a都會重新初始化為0x00;然后執(zhí)行a++;而局部靜態(tài)變量在調(diào)用過程中卻能維持其值不變。
通常利用這個特性可以統(tǒng)計一個函數(shù)被調(diào)用的次數(shù)。
聲明函數(shù)的一個局部變量,并設(shè)為static類型,作為一個計數(shù)器,這樣函數(shù)每次被調(diào)用的時候就可以進行計數(shù)。這是統(tǒng)計函數(shù)被調(diào)用次數(shù)的最好的辦法,因為這個變量是和函數(shù)息息相關(guān)的,而函數(shù)可能在多個不同的地方被調(diào)用,所以從調(diào)用者的角度來統(tǒng)計比較困難。代碼如下:void count();int main(){ int i; for (i = 1; i <= 3; i++)
{ count();
{ return 0;}void count(){ static num = 0; num++; printf(" I have been called %d",num,"times/n");}
輸出結(jié)果為:I have been called 1 times.I have been called 2 times.I have been called 3 times.
看一下局部靜態(tài)變量的詳細特性,注意它的作用域。
1)內(nèi)存中的位置:靜態(tài)存儲區(qū)
2)初始化:未經(jīng)初始化的全局靜態(tài)變量會被程序自動初始化為0(自動對象的值是任意的,除非他被顯示初始化)
3)作用域:作用域仍為局部作用域,當定義它的函數(shù)或者語句塊結(jié)束的時候,作用域隨之結(jié)束。
注:當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置,從原來的棧中存放改為靜態(tài)存儲區(qū)。但是局部靜態(tài)變量在離開作用域之后,并沒有被銷毀,而是仍然駐留在內(nèi)存當中,直到程序結(jié)束,只不過我們不能再對他進行訪問。
作用二:在模塊內(nèi)(但在函數(shù)體外),一個被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問,但不能被模塊外其它函數(shù)訪問。它是一個本地的全局變量。
這樣定義的變量也稱為全局靜態(tài)變量:在全局變量之前加上關(guān)鍵字static,全局變量就被定義成為一個全局靜態(tài)變量。也就是上述作用二中提到的在模塊內(nèi)(但在函數(shù)體外)聲明的靜態(tài)變量。
定義全局靜態(tài)變量的好處:
<1>不會被其他文件所訪問,修改,是一個本地的局部變量。
<2>其他文件中可以使用相同名字的變量,不會發(fā)生沖突。
全局變量的詳細特性,注意作用域,可以和局部靜態(tài)變量相比較:
1)內(nèi)存中的位置:靜態(tài)存儲區(qū)(靜態(tài)存儲區(qū)在整個程序運行期間都存在)
2)初始化:未經(jīng)初始化的全局靜態(tài)變量會被程序自動初始化為0(自動對象的值是任意的,除非他被顯示初始化)
3)作用域:全局靜態(tài)變量在聲明他的文件之外是不可見的。準確地講從定義之處開始到文件結(jié)尾。
當static用來修飾全局變量的時候,它就改變了全局變量的作用域(在聲明他的文件之外是不可見的),但是沒有改變它的存放位置,還是在靜態(tài)存儲區(qū)中。
作用三:在模塊內(nèi),一個被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是,這個函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。
這樣定義的函數(shù)也成為靜態(tài)函數(shù):在函數(shù)的返回類型前加上關(guān)鍵字static,函數(shù)就被定義成為靜態(tài)函數(shù)。函數(shù)的定義和聲明默認情況下是extern的,但靜態(tài)函數(shù)只是在聲明他的文件當中可見,不能被其他文件所用。
定義靜態(tài)函數(shù)的好處:
<1> 其他文件中可以定義相同名字的函數(shù),不會發(fā)生沖突
<2> 靜態(tài)函數(shù)不能被其他文件所用。它定義一個本地的函數(shù)。
這里我一直強調(diào)數(shù)據(jù)和函數(shù)的本地化,這對于程序的結(jié)構(gòu)甚至優(yōu)化都有巨大的好處,更大的作用是,本地化的數(shù)據(jù)和函數(shù)能給人傳遞很多有用的信息,能約束數(shù)據(jù)和函數(shù)的作用范圍。在C++的對象和類中非常注重的私有和公共數(shù)據(jù)/函數(shù)其實就是本地和全局數(shù)據(jù)/函數(shù)的擴展,這也從側(cè)面反應了本地化數(shù)據(jù)/函數(shù)的優(yōu)勢。
最后說一下存儲說明符,在標準C語言中,存儲說明符有以下幾類:
auto、register、extern和static
對應兩種存儲期:自動存儲期和靜態(tài)存儲期。auto和register對應自動存儲期。具有自動存儲期的變量在進入聲明該變量的程序塊時被建立,它在該程序塊活動時存在,退出該程序塊時撤銷。
關(guān)鍵字extern和static用來說明具有靜態(tài)存儲期的變量和函數(shù)。用static聲明的局部變量具有靜態(tài)存儲持續(xù)期(static storage duration),或靜態(tài)范圍(static extent)。雖然他的值在函數(shù)調(diào)用之間保持有效,但是其名字的可視性仍限制在其局部域內(nèi)。靜態(tài)局部對象在程序執(zhí)行到該對象的聲明處時被首次初始化。
2. const關(guān)鍵字
const關(guān)鍵字也是一個優(yōu)秀程序中經(jīng)常用到的關(guān)鍵字。關(guān)鍵字const 的作用是為給讀你代碼的人傳達非常有用的信息,實際上,聲明一個參數(shù)為常量是為了告訴了用戶這個參數(shù)的應用目的。通過給優(yōu)化器一些附加的信息,使用關(guān)鍵字const 也許能產(chǎn)生更緊湊的代碼。合理地使用關(guān)鍵字const 可以使編譯器很自然地保護那些不希望被改變的參數(shù),防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現(xiàn)。
深入理解const關(guān)鍵字,你必須知道:
a. const關(guān)鍵字修飾的變量可以認為有只讀屬性,但它絕不與常量劃等號。如下代碼:
const int i=5;
int j=0;
...
i=j; //非法,導致編譯錯誤,因為只能被讀
j=i; //合法
b. const關(guān)鍵字修飾的變量在聲明時必須進行初始化。如下代碼:
const int i=5; //合法
const int j; //非法,導致編譯錯誤
c. 用const聲明的變量雖然增加了分配空間,但是可以保證類型安全。const最初是從C++變化得來的,它可以替代define來定義常量。在舊版本(標準前)的c中,如果想建立一個常量,必須使用預處理器:#define PI 3.14159
此后無論在何處使用PI,都會被預處理器以3.14159替代。編譯器不對PI進行類型檢查,也就是說可以不受限制的建立宏并用它來替代值,如果使用不慎,很可能由預處理引入錯誤,這些錯誤往往很難發(fā)現(xiàn)。而且,我們也不能得到PI的地址(即不能向PI傳遞指針和引用)。const的出現(xiàn),比較好的解決了上述問題。
d. C標準中,const定義的常量是全局的。
e. 必須明白下面語句的含義,我自己是反復記憶了許久才記住,方法是:若是想定義一個只讀屬性的指針,那么關(guān)鍵字const要放到‘* ’后面。
char *const cp; //指針不可改變,但指向的內(nèi)容可以改變
char const *pc1; //指針可以改變,但指向的內(nèi)容不能改變
const char *pc2; //同上(后兩個聲明是等同的)
f. 將函數(shù)傳入?yún)?shù)聲明為const,以指明使用這種參數(shù)僅僅是為了效率的原因,而不是想讓調(diào)用函數(shù)能夠修改對象的值。
參數(shù)const通常用于參數(shù)為指針或引用的情況,且只能修飾輸入?yún)?shù);若輸入?yún)?shù)采用“值傳遞”方式,由于函數(shù)將自動產(chǎn)生臨時變量用于復制該參數(shù),該參數(shù)本就不需要保護,所以不用const修飾。例子:
void fun0(const int * a );
void fun1(const int & a);
調(diào)用函數(shù)的時候,用相應的變量初始化const常量,則在函數(shù)體中,按照const所修飾的部分進行常量化,如形參為const int * a,則不能對傳遞進來的指針所指向的內(nèi)容進行改變,保護了原指針所指向的內(nèi)容;如形參為const int & a,則不能對傳遞進來的引用對象進行改變,保護了原對象的屬性。
g. 修飾函數(shù)返回值,可以阻止用戶修改返回值。(在嵌入式C中一般不用,主要用于C++)
h. const消除了預處理器的值替代的不良影響,并且提供了良好的類型檢查形式和安全性,在可能的地方盡可能的使用const對我們的編程有很大的幫助,前提是:你對const有了足夠的理解。
最后,舉兩個常用的標準C庫函數(shù)聲明,它們都是使用const的典范。
1.字符串拷貝函數(shù):char *strcpy(char *strDest,const char *strSrc);
2.返回字符串長度函數(shù):int strlen(const char *str);
3.volatile關(guān)鍵字
一個定義為volatile 的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設(shè)這個變量的值了。精確地說就是,優(yōu)化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。
由于訪問寄存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優(yōu)化。比如:
static int i=0;int main(void){...while (1){if (i)dosomething();}}/* Interrupt service routine. */void ISR_2(void){ i=1;} 程序的本意是希望ISR_2中斷產(chǎn)生時,在main當中調(diào)用dosomething函數(shù),但是,由于編譯器判斷在main函數(shù)里面沒有修改過i,因此可能只執(zhí)行一次對從i到某寄存器的讀操作,然后每次if判斷都只使用這個寄存器里面的“i副本”,導致dosomething永遠也不會被調(diào)用。如果將將變量加上volatile修飾,則編譯器保證對此變量的讀寫操作都不會被優(yōu)化(肯定執(zhí)行)。此例中i也應該如此說明。一般說來,volatile用在如下的幾個地方:1、中斷服務(wù)程序中修改的供其它程序檢測的變量需要加volatile;2、多任務(wù)環(huán)境下各任務(wù)間共享的標志應該加volatile;3、存儲器映射的硬件寄存器通常也要加volatile說明,因為每次對它的讀寫都可能有不同意義;
不懂得volatile 的內(nèi)容將會帶來災難,這也是區(qū)分C語言和嵌入式C語言程序員的一個關(guān)鍵因素。為強調(diào)volatile的重要性,再次舉例分析:
代碼一:
int a,b,c;
//讀取I/O空間0x100端口的內(nèi)容
a= inword(0x100);
b=a;
a=inword(0x100)
c=a;
代碼二:
volatile int a;
int a,b,c;
//讀取I/O空間0x100端口的內(nèi)容
a= inword(0x100);
b=a;
a=inword(0x100)
c=a;
在上述例子中,代碼一會被絕大多數(shù)編譯器優(yōu)化為如下代碼:
a=inword(0x100)
b=a;
c=a;
這顯然與編寫者的目的不相符,會出現(xiàn)I/O空間0x100端口漏讀現(xiàn)象,若是增加volatile,像代碼二所示的那樣,優(yōu)化器將不會優(yōu)化掉任何代碼.
從上面來看,volatile關(guān)鍵字是會降低編譯器優(yōu)化力度的,但它保證了程序的正確性,所以在適合的地方使用關(guān)鍵字volatile是件考驗編程功底的事情.
4.struct與typedef關(guān)鍵字
面對一個人的大型C/C++程序時,只看其對struct的使用情況我們就可以對其編寫者的編程經(jīng)驗進行評估。因為一個大型的C/C++程序,勢必要涉及一些(甚至大量)進行數(shù)據(jù)組合的結(jié)構(gòu)體,這些結(jié)構(gòu)體可以將原本意義屬于一個整體的數(shù)據(jù)組合在一起。從某種程度上來說,會不會用struct,怎樣用struct是區(qū)別一個開發(fā)人員是否具備豐富開發(fā)經(jīng)歷的標志。
在網(wǎng)絡(luò)協(xié)議、通信控制、嵌入式系統(tǒng)的C/C++編程中,我們經(jīng)常要傳送的不是簡單的字節(jié)流(char型數(shù)組),而是多種數(shù)據(jù)組合起來的一個整體,其表現(xiàn)形式是一個結(jié)構(gòu)體。
經(jīng)驗不足的開發(fā)人員往往將所有需要傳送的內(nèi)容依順序保存在char型數(shù)組中,通過指針偏移的方法傳送網(wǎng)絡(luò)報文等信息。這樣做編程復雜,易出錯,而且一旦控制方式及通信協(xié)議有所變化,程序就要進行非常細致的修改。
用法:
在C中定義一個結(jié)構(gòu)體類型要用typedef:typedef struct Student{ int a;}Stu;于是在聲明變量的時候就可:Stu stu1;如果沒有typedef就必須用struct Student stu1;來聲明這里的Stu實際上就是struct Student的別名。另外這里也可以不寫Student(于是也不能struct Student stu1;了)typedef struct{ int a;}Stu;
struct關(guān)鍵字的一個總要作用是它可以實現(xiàn)對數(shù)據(jù)的封裝,有一點點類似與C++的對象,可以將一些分散的特性對象化,這在編寫某些復雜程序時提供很大的方便性.
比如編寫一個菜單程序,你要知道本級菜單的菜單索引號、焦點在屏上是第幾項、顯示第一項對應的菜單條目索引、菜單文本內(nèi)容、子菜單索引、當前菜單執(zhí)行的功能操作。若是對上述條目單獨操作,那么程序的復雜程度將會大到不可想象,若是菜單層數(shù)少些還容易實現(xiàn),一旦菜單層數(shù)超出四層,呃~我就沒法形容了。若是有編寫過菜單程序的朋友或許理解很深。這時候結(jié)構(gòu)體struct就開始顯現(xiàn)它的威力了:
//結(jié)構(gòu)體定義
typedef struct
{
unsigned char CurrentPanel;//本級菜單的菜單索引號
unsigned char ItemStartDisplay; //顯示第一項對應的菜單條目索引
unsigned char FocusLine; //焦點在屏上是第幾項
}Menu_Statestruct;
typedef struct
{
unsigned char *MenuTxt; //菜單文本內(nèi)容
unsigned char MenuChildID;//子菜單索引
void (*CurrentOperate)();//當前菜單執(zhí)行的功能操作
}MenuItemStruct;
typedef struct
{
MenuItemStruct *MenuPanelItem;
unsigned char MenuItemCount;
}MenuPanelStruct;
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19104瀏覽量
304807 -
C語言
+關(guān)注
關(guān)注
180文章
7604瀏覽量
136687 -
static
+關(guān)注
關(guān)注
0文章
33瀏覽量
10366 -
CONST
+關(guān)注
關(guān)注
0文章
44瀏覽量
8159
原文標題:嵌入式C語言不可不用的關(guān)鍵字
文章出處:【微信號:wujianying_danpianji,微信公眾號:單片機精講吳鑒鷹】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論