C語言關(guān)鍵字應(yīng)用技巧
1、volatile
volatile修飾表示變量是易變的,編譯器中的優(yōu)化器在用到這個變量時必須每次都小心地從內(nèi)存中重新讀取這個變量的值,而不是使用保存在寄存器里的備份,有效的防止編譯器自動優(yōu)化,從而與軟件設(shè)計(jì)相符合。
中斷服務(wù)與主程序共享變量:
- //volatile uint8_t flag=1;
- uint8_t flag=1;
- void test(void)
- {
- while(flag)
- {
- //do something
- }
- }
- //interrupt service routine
- void isr_test(void)
- {
- flag=0;
- }
如果沒使用volatile定義flag,可能在優(yōu)化后test陷入死循環(huán),因?yàn)閠est里使用的flag并沒修改它,開啟優(yōu)化后,編譯器可能會固定從某個內(nèi)存取值。例如:
- for(int i=0; i<100000; i++);
- //對比
- for(volatile int i=0; i<100000; i++);
前者可能被優(yōu)化掉,雖然編碼本意是需要執(zhí)行操作延時,但編譯器認(rèn)為代碼無意義。
總的來說,volatile是告知編譯器,不管代碼如何,必須保留,而且使用時需要重新從內(nèi)存讀取更新,不能使用先前讀取的緩存,一般在驅(qū)動代碼中使用較多。
2、const
const是恒定不變的意思,其修飾的各種數(shù)據(jù)類似只讀效果。
1)修飾變量
采用const修飾變量,即變量聲明為只讀,保護(hù)變量值以防被修改。例如
- const int i = 1;
上面這個例子表明,變量i具有只讀特性,不能夠被更改;若想對i重新賦值,如i = 10;屬于錯誤操作。
特別說明,定義變量的同時進(jìn)行初始化,寫成int const i=1,是正確的。
2)修飾數(shù)組
C語言中const還可以修飾數(shù)組,舉例如下:
- const int array[5] = {1,2,3,4,5};
- array[0] = array[0]+1; //錯誤,array是只讀的,禁止修改
數(shù)組元素與變量類似,具有只讀屬性,不能被更改;一旦更改,編譯時就會報錯。
使用大數(shù)組存儲固定的信息,例如查表(表驅(qū)動法的鍵值表),可以使用const節(jié)省ram。編譯器并不給普通const只讀變量分配空間,而是將它們保存到符號表中,無需讀寫內(nèi)存操作,程序執(zhí)行效率也會提高。
3)修飾指針
C語言中const修飾指針要特別注意,共有兩種形式,一種是用來限定指向空間的值不能修改;另一種是限定指針不可更改。舉例如下:
- int i = 1;
- int j = 2;
- const int *p1 = &i;
- int* const p2 = &j;
上面定義了兩個指針p1和p2,區(qū)別是const后面是指針本身還是指向的內(nèi)容。
在定義1中const限定的是* p1,即其指向空間的值不可改變,若改變其指向空間的值如* p1=10,則程序會報錯;但p1的值是可以改變的,對p1重新賦值如p1=&k是沒有任何問題的。
在定義2中const限定的是指針p2,若改變p2的值如p2=&k,程序?qū)箦e;但* p2,即其所指向空間的值可以改變,如* p2=20是沒有問題的,程序正常執(zhí)行。
4)修飾函數(shù)參數(shù)
const關(guān)鍵字修飾函數(shù)參數(shù),對參數(shù)起限定作用,防止其在函數(shù)內(nèi)部被修改。所限定的函數(shù)參數(shù)可以是普通變量,也可以是指針變量。例如:
- void fun(const int i)
- {
- ……
- i++; //對i的值進(jìn)行了修改,程序報錯
- }
常用的函數(shù)如strlen
- size_t strlen(const char *string);
const在庫函數(shù)中使用非常普遍,是一種自我保護(hù)的安全編碼思維。
3、struct與union
對于struct 結(jié)構(gòu)體和union共聯(lián)體在嵌入式領(lǐng)域是使用得非常頻繁的,一些可編程芯片提供的寄存器庫都是采用結(jié)構(gòu)體和共聯(lián)體結(jié)合的方式來提供給軟件人員進(jìn)行開發(fā),同時在平時的編碼過程中這兩個數(shù)據(jù)類型的靈活應(yīng)用也能夠?qū)崿F(xiàn)代碼更好的封裝與簡化。
如下面的簡單示例,就可以非常靈活的訪問Val中的bit位。
- typedef union
- {
- BYTE Val;
- struct __packed
- {
- BYTE b0:1;
- BYTE b1:1;
- BYTE b2:1;
- BYTE b3:1;
- BYTE b4:1;
- BYTE b5:1;
- BYTE b6:1;
- BYTE b7:1;
- } bits;
- }BYTE_VAL, BYTE_BITS;
其中:1表示按位操作。不只是位-字節(jié)可以,單字節(jié)與多字節(jié)也可以簡化拼接。
- #include "stdio.h"
- typedef struct
- {
- union
- {
- struct
- {
- unsigned char low;
- unsigned char high;
- };
- unsigned short result;
- };
- }test_t;
- int main(int argc, char *argv[])
- {
- test_t hello;
- hello.high=0x12;
- hello.low=0x34;
- printf("result=%04X\r\n",hello.result);//輸出 result=1234
- return 0;
- }
運(yùn)行輸出 result=1234 (win7系統(tǒng)下QT開發(fā)環(huán)境),原本需要 (high<<8)|low 運(yùn)算,可以簡化為共用體類型自動完成,但必須注意平臺的字節(jié)順序,屬于大端還是小端模式。
在應(yīng)用層面,如果明確某個數(shù)據(jù)可能存在兩種可能,而且兩種結(jié)果不會同時存在,也可以使用結(jié)構(gòu)體與共用體組合的方式,確保模塊對外接口統(tǒng)一。
例如移動通信模塊,使用數(shù)據(jù)結(jié)構(gòu)保存其基站信息,因?yàn)橹剖讲煌?,模塊可能工作在2G-GSM,也可能在4G-Cat1,為保證上層讀取基站信息接口唯一,使用共用體就非常合適,否則需定義兩套接口。如果覺得文章可以,可關(guān)注微信公眾號【嵌入式系統(tǒng)】獲取更多信息。
4、預(yù)定義標(biāo)識符
一般編譯器都支持預(yù)定義標(biāo)識符,這些標(biāo)識符結(jié)合printf等打印信息幫助程序員調(diào)試程序是非常有用的,一般編譯器會自動根據(jù)用戶指定完成替換和處理。
部分標(biāo)識:
- __FILE__ //表示編譯的源文件名
- __LINE__ //表示當(dāng)前文件的行號
- __FUNCTION__ //表示函數(shù)名
- __DATE__ //表示編譯日期
- __TIME__ //表示編譯時間
使用范例:
- printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);
這些比較常見,主要用于日志分析、版本記錄,便于調(diào)試。
5、#與##
#:是一種運(yùn)算符,用于帶參宏的文本替換,將跟在后面的參數(shù)轉(zhuǎn)成一個字符串常量。
##:是一種運(yùn)算符,是將兩個運(yùn)算對象連接在一起,也只能出現(xiàn)在帶參宏定義的文本替換中。
- #include "stdio.h"
- #define TO_STR(s) #s
- #define COMB(str1,str2) str1##str2
- int main(int argc, char *argv[])
- {
- int UART0= 115200;
- printf("UART0=%d\n", COMB(UART, 0));//字符串合并為變量UART0
- printf("%s\n", TO_STR(3.14));//將數(shù)字變成字符串
- return 0;
- }
6、void 與 void*
void表示的是無類型,不能聲明變量或常量,但是可以把指針定義為void類型,如void* ptr。void* 指針可以指向任意類型的數(shù)據(jù),在C語言指針操作中,任意類型的數(shù)據(jù)地址都可轉(zhuǎn)為void* 指針。因?yàn)橹羔槺举|(zhì)上都是unsigned int。
常用的內(nèi)存塊操作庫函數(shù):
- void * memcpy( void *dest, const void *src, size_t len );
- void * memset( void *buffer, int c, size_t num);
數(shù)據(jù)指針為void* 類型,對傳入任意類型數(shù)據(jù)的指針都可以操作。另外其中memcpy第二個參數(shù),const現(xiàn)在也如前文所述,拷貝時對傳入的原數(shù)據(jù)內(nèi)容禁止修改。
特殊說明,指針是不能使用sizeof求內(nèi)容大小的,在ARM系統(tǒng)固定為int 4字節(jié)。對于函數(shù)無輸入?yún)?shù)的,也盡量加上void,如
- void fun(void);
7、weak
一般簡化定義
- #define _WEAK __attribute__((weak))
函數(shù)名稱前面加上__WEAK屬性修飾符稱為“弱函數(shù)”,類似C++的虛函數(shù)。鏈接時優(yōu)先鏈接為非weak定義的函數(shù),如果找不到則再鏈接帶weak函數(shù)。
- _WEAK void fun(void)
- {
- //do this
- }
- //不在同一個.c,兩同名函數(shù)不能在同一個文件
- void fun(void)
- {
- //do that
- }
這種自動選擇的機(jī)制,在代碼移植和多模塊配合工作的場景下應(yīng)用較多。例如前期移植代碼,需要調(diào)用某個接口fun,但當(dāng)前該接口不存在或者未移植完整使用,可以使用weak關(guān)鍵字定義為空函數(shù)先保證編譯正常。后續(xù)移植完成實(shí)現(xiàn)了fun,即軟件中有2個fun函數(shù)沒有任何錯誤,編譯器自動會識別使用后者。當(dāng)然也粗暴的#if 0屏蔽對fun的調(diào)用,但要確保后續(xù)記得放開。
8、總結(jié)
存在即合理,C語言里面的關(guān)鍵字,每個都有其特殊的意義,只是一般使用較少,譬如作文,使用常用的漢字可以;但引經(jīng)據(jù)典,使用特殊的修飾辭藻更能顯出水平。后續(xù)對section 進(jìn)行詳細(xì)說明,它和動態(tài)加載(OTA)、接口自啟動相關(guān)。
本文轉(zhuǎn)載自微信公眾號「嵌入式系統(tǒng)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系嵌入式系統(tǒng)公眾號。