• 嵌入式C語言不可不用的關鍵字

    -回復 -瀏覽
    樓主 2020-07-14 06:58:05
    舉報 只看此人 收藏本貼 樓主

    1.static關鍵字


    這個關鍵字的作用是餓很難強大的。

    要對static關鍵字深入了解,首先需要掌握標準C程序的組成。


    標準C程序一直由下列部分組成:

    ???????

    1)正文段——CPU執行的機器指令部分,也就是你的程序。一個程序只有一個副本;只讀,這是為了防止程序由于意外事故而修改自身指令;
    ???????

    2)初始化數據段(數據段)——在程序中所有賦了初值的全局變量,存放在這里。
    ???????

    3)非初始化數據段(bss段)——在程序中沒有初始化的全局變量;內核將此段初始化為0。


    注意:只有全局變量被分配到數據段中。
    ???????

    4)?!鲩L方向:自頂向下增長;自動變量以及每次函數調用時所需要保存的信息(返回地址;環境信息)。這句很關鍵,常常有筆試題會問到什么東西放到棧里面就足以說明。
    ???????

    5)堆——動態存儲分配。

    ?

    在嵌入式C語言當中,它有三個作用:


    作用一:在函數體,一個被聲明為靜態的變量在這一函數被調用過程中維持其值不變。


    這樣定義的變量稱為局部靜態變量:在局部變量之前加上關鍵字static,局部變量就被定義成為一個局部靜態變量。也就是上面的作用一中提到的在函數體內定義的變量。除了類型符外,若不加其它關鍵字修飾,默認都是局部變量。比如以下代碼:


    void test1(void)

    {

    ????unsigned char a;

    ????static unsigned char b;

    ????…

    ????a++;

    ????b++;

    }


    在這個例子中,變量a是局部變量,變量b為局部靜態變量。作用一說明了局部靜態變量b的特性:在函數體,一個被聲明為靜態的變量(也就是局部靜態變量)在這一函數被調用過程中維持其值不變。這句話什么意思呢?若是連續兩次調用上面的函數test1:


    ????void main(void)

    ????{

    ???????…

    ???????test1();

    ???????test1();

    ???????…

    ????}


    然后使程序暫停下來,讀取a和b的值,你會發現,a=1,b=2。怎么回事呢,每次調用test1函數,局部變量a都會重新初始化為0x00;然后執行a++;而局部靜態變量在調用過程中卻能維持其值不變。


    通常利用這個特性可以統計一個函數被調用的次數。


    聲明函數的一個局部變量,并設為static類型,作為一個計數器,這樣函數每次被調用的時候就可以進行計數。這是統計函數被調用次數的最好的辦法,因為這個變量是和函數息息相關的,而函數可能在多個不同的地方被調用,所以從調用者的角度來統計比較困難。代碼如下:

    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");
    }


    輸出結果為:
    I have been called 1 times.
    I have been called 2 times.
    I have been called 3 times.

    ?

    看一下局部靜態變量的詳細特性,注意它的作用域。


    1)內存中的位置:靜態存儲區


    2)初始化:未經初始化的全局靜態變量會被程序自動初始化為0(自動對象的值是任意的,除非他被顯示初始化)


    3)作用域:作用域仍為局部作用域,當定義它的函數或者語句塊結束的時候,作用域隨之結束。

    ??

    注:當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置,從原來的棧中存放改為靜態存儲區。但是局部靜態變量在離開作用域之后,并沒有被銷毀,而是仍然駐留在內存當中,直到程序結束,只不過我們不能再對他進行訪問。

    ?

    作用二:在模塊內(但在函數體外),一個被聲明為靜態的變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問。它是一個本地的全局變量。


    這樣定義的變量也稱為全局靜態變量:在全局變量之前加上關鍵字static,全局變量就被定義成為一個全局靜態變量。也就是上述作用二中提到的在模塊內(但在函數體外)聲明的靜態變量。


    定義全局靜態變量的好處:

    <1>不會被其他文件所訪問,修改,是一個本地的局部變量。


    <2>其他文件中可以使用相同名字的變量,不會發生沖突。


    全局變量的詳細特性,注意作用域,可以和局部靜態變量相比較:


    1)內存中的位置:靜態存儲區(靜態存儲區在整個程序運行期間都存在)

    ????

    2)初始化:未經初始化的全局靜態變量會被程序自動初始化為0(自動對象的值是任意的,除非他被顯示初始化)

    ????

    3)作用域:全局靜態變量在聲明他的文件之外是不可見的。準確地講從定義之處開始到文件結尾。


    當static用來修飾全局變量的時候,它就改變了全局變量的作用域(在聲明他的文件之外是不可見的),但是沒有改變它的存放位置,還是在靜態存儲區中。

    ?

    作用三:在模塊內,一個被聲明為靜態的函數只可被這一模塊內的其它函數調用。那就是,這個函數被限制在聲明它的模塊的本地范圍內使用。

    ?

    這樣定義的函數也成為靜態函數:在函數的返回類型前加上關鍵字static,函數就被定義成為靜態函數。函數的定義和聲明默認情況下是extern的,但靜態函數只是在聲明他的文件當中可見,不能被其他文件所用。


    定義靜態函數的好處:

    <1>?其他文件中可以定義相同名字的函數,不會發生沖突


    <2>?靜態函數不能被其他文件所用。它定義一個本地的函數。


    這里我一直強調數據和函數的本地化,這對于程序的結構甚至優化都有巨大的好處,更大的作用是,本地化的數據和函數能給人傳遞很多有用的信息,能約束數據和函數的作用范圍。在C++的對象和類中非常注重的私有和公共數據/函數其實就是本地和全局數據/函數的擴展,這也從側面反應了本地化數據/函數的優勢。

    ?

    最后說一下存儲說明符,在標準C語言中,存儲說明符有以下幾類:


    auto、register、extern和static


    對應兩種存儲期:自動存儲期和靜態存儲期。


    auto和register對應自動存儲期。具有自動存儲期的變量在進入聲明該變量的程序塊時被建立,它在該程序塊活動時存在,退出該程序塊時撤銷。


    關鍵字extern和static用來說明具有靜態存儲期的變量和函數。用static聲明的局部變量具有靜態存儲持續期(static storage duration),或靜態范圍(static extent)。雖然他的值在函數調用之間保持有效,但是其名字的可視性仍限制在其局部域內。靜態局部對象在程序執行到該對象的聲明處時被首次初始化。

    2. const?關鍵字


    const關鍵字也是一個優秀程序中經常用到的關鍵字。關鍵字const?的作用是為給讀你代碼的人傳達非常有用的信息,實際上,聲明一個參數為常量是為了告訴了用戶這個參數的應用目的。通過給優化器一些附加的信息,使用關鍵字const?也許能產生更緊湊的代碼。合理地使用關鍵字const?可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現。


    深入理解const關鍵字,你必須知道:


    a. const關鍵字修飾的變量可以認為有只讀屬性,但它絕不與常量劃等號。

    如下代碼:


    const int i=5;

    ???????int j=0;

    ???????...

    ???????i=j;?? //非法,導致編譯錯誤,因為只能被讀

    ???????j=i;?? //合法


    b. const關鍵字修飾的變量在聲明時必須進行初始化。如下代碼:


    const int i=5;??? //合法

    ?????const int j;????? //非法,導致編譯錯誤


    c.?用const聲明的變量雖然增加了分配空間,但是可以保證類型安全。const最初是從C++變化得來的,它可以替代define來定義常量。在舊版本(標準前)的c中,如果想建立一個常量,必須使用預處理器:


    ????????????????#define PI 3.14159


    此后無論在何處使用PI,都會被預處理器以3.14159替代。編譯器不對PI進行類型檢查,也就是說可以不受限制的建立宏并用它來替代值,如果使用不慎,很可能由預處理引入錯誤,這些錯誤往往很難發現。而且,我們也不能得到PI的地址(即不能向PI傳遞指針和引用)。const的出現,比較好的解決了上述問題。


    d. C標準中,const定義的常量是全局的。


    e.?必須明白下面語句的含義,我自己是反復記憶了許久才記住,方法是:若是想定義一個只讀屬性的指針,那么關鍵字const要放到‘*?’后面。


    char *const cp; //指針不可改變,但指向的內容可以改變


    char const *pc1; //指針可以改變,但指向的內容不能改變


    const char *pc2; //同上(后兩個聲明是等同的)

    ???????

    f.?將函數傳入參數聲明為const,以指明使用這種參數僅僅是為了效率的原因,而不是想讓調用函數能夠修改對象的值。

    ???????

    參數const通常用于參數為指針或引用的情況,且只能修飾輸入參數;若輸入參數采用“值傳遞”方式,由于函數將自動產生臨時變量用于復制該參數,該參數本就不需要保護,所以不用const修飾。例子:


    void fun0(const??int * a );

    void fun1(const??int & a);


    調用函數的時候,用相應的變量初始化const常量,則在函數體中,按照const所修飾的部分進行常量化,如形參為const int * a,則不能對傳遞進來的指針所指向的內容進行改變,保護了原指針所指向的內容;如形參為const int & a,則不能對傳遞進來的引用對象進行改變,保護了原對象的屬性。

    ???????

    g.?修飾函數返回值,可以阻止用戶修改返回值。(在嵌入式C中一般不用,主要用于C++)

    ???????

    h. const消除了預處理器的值替代的不良影響,并且提供了良好的類型檢查形式和安全性,在可能的地方盡可能的使用const對我們的編程有很大的幫助,前提是:你對const有了足夠的理解。

    ???????

    最后,舉兩個常用的標準C庫函數聲明,它們都是使用const的典范。

    ???????

    1.字符串拷貝函數:char *strcpy(char *strDest,const char *strSrc);

    ???????

    2.返回字符串長度函數:int strlen(const char *str);

    3.?volatile關鍵字


    一個定義為volatile?的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。


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


    static int i=0;?

    int main(void)?
    {?
    ????...?
    ????while (1)?
    ????{?
    ????????if (i)?
    ????????????dosomething();?
    ????}?
    }?

    /* Interrupt service routine. */?
    void ISR_2(void)?
    {?
    ???? i=1;?
    }?


    程序的本意是希望ISR_2中斷產生時,在main當中調用dosomething函數,但是,由于編譯器判斷在main函數里面沒有修改過i,因此可能只執行一次對從i到某寄存器的讀操作,然后每次if判斷都只使用這個寄存器里面的“i副本”,導致dosomething永遠也不會被調用。?

    如果將將變量加上volatile修飾,則編譯器保證對此變量的讀寫操作都不會被優化(肯定執行)。此例中i也應該如此說明。?

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

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

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

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


    不懂得volatile?的內容將會帶來災難,這也是區分C語言和嵌入式C語言程序員的一個關鍵因素。為強調volatile的重要性,再次舉例分析:

    代碼一:????????

    ???????????????????????

    int a,b,c;?????????????????????????????

    //讀取I/O空間0x100端口的內容????

    a= inword(0x100);??????????????????????????????????????????????????

    b=a;???????????????????????????????

    a=inword(0x100)????????????????????????????

    c=a;??


    代碼二:???


    ??volatile int a;???

    ??int a,b,c;?????????????????????????????

    //讀取I/O空間0x100端口的內容????

    a= inword(0x100);???????????????????????????????????????????????

    b=a;???????????????????????????????

    a=inword(0x100)????????????????????????

    c=a;?


    在上述例子中,代碼一會被絕大多數編譯器優化為如下代碼:


    ???????a=inword(0x100)

    ???????b=a;

    ???????c=a;


    這顯然與編寫者的目的不相符,會出現I/O空間0x100端口漏讀現象,若是增加volatile,像代碼二所示的那樣,優化器將不會優化掉任何代碼.

    ???????

    從上面來看,volatile關鍵字是會降低編譯器優化力度的,但它保證了程序的正確性,所以在適合的地方使用關鍵字volatile是件考驗編程功底的事情.

    4.struct與typedef關鍵字


    面對一個人的大型C/C++程序時,只看其對struct的使用情況我們就可以對其編寫者的編程經驗進行評估。因為一個大型的C/C++程序,勢必要涉及一些(甚至大量)進行數據組合的結構體,這些結構體可以將原本意義屬于一個整體的數據組合在一起。從某種程度上來說,會不會用struct,怎樣用struct是區別一個開發人員是否具備豐富開發經歷的標志。

      

    在網絡協議、通信控制、嵌入式系統的C/C++編程中,我們經常要傳送的不是簡單的字節流(char型數組),而是多種數據組合起來的一個整體,其表現形式是一個結構體。

    經驗不足的開發人員往往將所有需要傳送的內容依順序保存在char型數組中,通過指針偏移的方法傳送網絡報文等信息。這樣做編程復雜,易出錯,而且一旦控制方式及通信協議有所變化,程序就要進行非常細致的修改。

    用法:


    在C中定義一個結構體類型要用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關鍵字的一個總要作用是它可以實現對數據的封裝,有一點點類似與C++的對象,可以將一些分散的特性對象化,這在編寫某些復雜程序時提供很大的方便性.


    比如編寫一個菜單程序,你要知道本級菜單的菜單索引號、焦點在屏上是第幾項、顯示第一項對應的菜單條目索引、菜單文本內容、子菜單索引、當前菜單執行的功能操作。若是對上述條目單獨操作,那么程序的復雜程度將會大到不可想象,若是菜單層數少些還容易實現,一旦菜單層數超出四層,呃~我就沒法形容了。若是有編寫過菜單程序的朋友或許理解很深。這時候結構體struct就開始顯現它的威力了:


    //結構體定義

    typedef struct

    {

    unsigned char CurrentPanel;//本級菜單的菜單索引號

    unsigned char ItemStartDisplay; //顯示第一項對應的菜單條目索引

    unsigned char FocusLine;??//焦點在屏上是第幾項

    }Menu_Statestruct;

    ?

    typedef struct

    {

    unsigned char *MenuTxt; //菜單文本內容

    unsigned char MenuChildID;//子菜單索引

    void????(*CurrentOperate)();//當前菜單執行的功能操作

    }MenuItemStruct;

    ?

    typedef struct

    {

    MenuItemStruct??*MenuPanelItem;

    unsigned char????MenuItemCount;

    }MenuPanelStruct;

    ?

    菜單程序中的結構體定義,這個菜單程序最大可以到256級菜單。當初要寫一個菜單程序之前,并沒有對結構體了解多少,也沒有想到使用結構體。只是一層層菜單的單獨處理:如果按鍵按下,判斷是哪個按鍵,調用對應按鍵的子程序,刷屏顯示。這樣處理起來每一層都要判斷當前的光標所在行,計算是不是在顯示屏的頂層,是不是在顯示層的底層,是不是需要翻頁等等,非常的繁瑣。

    ?

    后來在網上找資料,就找到了這個程序?;艘惶斓臅r間閱讀代碼和移植,才知道結構體原來有這么個妙用,當定義了上述三個結構體之后,菜單程序結構立刻變的簡單很多,思路也無比的清晰起來。


    長按識別二維碼

    關注嵌入式ARM



    長按識別二維碼

    關注21ic官方微信



    長按識別二維碼

    關注?電源Fan



    長按識別二維碼

    關注德州儀器公開課?



    我要推薦
    轉發到
    牛彩官网