嵌入式軟件開(kāi)發(fā)常用的關(guān)鍵字和運算符
1
volatile關(guān)鍵字
volatile是一個(gè)特征修飾符,提醒編譯器它后面所定義的變量隨時(shí)都有可能改變,因此編譯后的程序每次需要存儲或讀取這個(gè)變量的時(shí)候,告訴編譯器對該變量不做優(yōu)化,都會(huì )直接從變量?jì)却娴刂分凶x取數據,從而可以提供對特殊地址的穩定訪(fǎng)問(wèn)。
常用場(chǎng)景:中斷服務(wù)與主程序共享變量。示例代碼如下:
//volatile uint8_t flag=1;uint8_t flag=1;void test(void){ while(flag) { //do something }}//interrupt service routinevoid isr_test(void){ flag=0;}
如果沒(méi)使用volatile定義flag,可能在優(yōu)化后test陷入死循環(huán),因為test里使用的flag并沒(méi)修改它,開(kāi)啟優(yōu)化后,編譯器可能會(huì )固定從某個(gè)內存取值。
2
const關(guān)鍵字
const 是 constant 的縮寫(xiě),意思是“恒定不變的”,它是定義常變量的關(guān)鍵字。
通常有4種用法。
1、修飾變量
采用const修飾變量,即變量聲明為只讀,保護變量值以防被修改。
const int i = 1;或者int const i=1;
變量i具有只讀特性,不能夠被更改;若想對i重新賦值,如i = 10,屬于錯誤操作。
2、修飾數組
數組元素與變量類(lèi)似,具有只讀屬性,不能被更改,一旦更改,編譯時(shí)就會(huì )報錯。
const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //錯誤,array是只讀的,禁止修改
使用大數組存儲固定的信息,例如查表(表驅動(dòng)法的鍵值表),可以使用const節省ram。編譯器并不給普通const只讀變量分配空間,而是將它們保存到符號表中,無(wú)需讀寫(xiě)內存操作,程序執行效率也會(huì )提高。
3、修飾指針
C語(yǔ)言中const修飾指針要特別注意,共有兩種形式,一種是用來(lái)限定指向空間的值不能修改;另一種是限定指針不可更改。
int i = 1;int j = 2;const int *p1 = &i;int* const p2 = &j;
上面定義了兩個(gè)指針p1和p2,區別是const后面是指針本身還是指向的內容。
在定義1中const限定的是*p1,即其指向空間的值不可改變,若改變其指向空間的值如*p1=10,則程序會(huì )報錯;但p1的值是可以改變的,對p1重新賦值如p1=&k是沒(méi)有任何問(wèn)題的。
在定義2中const限定的是指針p2,若改變p2的值如p2=&k,程序將會(huì )報錯;但*p2,即其所指向空間的值可以改變,如*p2=20是沒(méi)有問(wèn)題的,程序正常執行。
4、 修飾函數參數
const關(guān)鍵字修飾函數參數,對參數起限定作用,防止其在函數內部被修改,所限定的函數參數可以是普通變量,也可以是指針變量。
void fun(const int i){ …… i++; //對i的值進(jìn)行了修改,程序報錯}
常用的函數如strlen。
size_t strlen(const char *string);
const在庫函數中使用非常普遍,是一種自我保護的安全編碼思維。
3
static關(guān)鍵字
1、static修飾全局變量,該變量只在本文件內被訪(fǎng)問(wèn),不能在其他文件被直接訪(fǎng)問(wèn)。
2、static修飾函數,該函數只能在本文件內被訪(fǎng)問(wèn),不能被其他文件訪(fǎng)問(wèn)。但是可以通過(guò)嵌套的方式調用,變相的封裝的表現。
3、static修飾局部變量,更改該局部變量的生命周期。
生命周期:將臨時(shí)變量的生命周期變成全局變量的生命周期。
作用域不變:作用域仍然是在本代碼塊內。
4
struct與union
可以使用struct結構體來(lái)存放一組不同類(lèi)型的數據。
struct 結構體名{ 結構體所包含的變量或數組};
結構體是一種集合,它里面包含了多個(gè)變量或數組,它們的類(lèi)型可以相同,也可以不同,每個(gè)這樣的變量或數組都稱(chēng)為結構體的成員,通常我們使用結構體定義和解析協(xié)議,如下所示:
// WiFi接收數據幀,控制切換模式#pragma pack(1)typedef struct receive_data_mode_t{ uint8_t device_head; // 數據幀頭:0XA0+功能碼(FUNCTION_ID3),A款產(chǎn)品智能插座 uint16_t device_len; // 數據包總長(cháng)度 uint16_t device_id; // 節點(diǎn)ID 0X0001~0XFFFE char software_version[15]; // 軟件版本 SMART_SW_A1_1.0 A款產(chǎn)品軟件1.0版本 char hardware_version[15]; // 硬件版本 SMART_HW_A1_1.0 A款產(chǎn)品硬件1.0版本 uint8_t switch_mode; // 切換模式 0:運行模式,1:配置模式,2:節點(diǎn)升級,3:節點(diǎn)重啟 uint16_t crc; // 校驗位}ReceiveData_Mode_t;#pragma pack()
union共用體關(guān)鍵字,定義union下面的成員變量共享一塊內存,每一個(gè)成員在任一時(shí)刻有且只有一個(gè)成員使用此塊內存。
union 共用體名{ 成員列表};
結構體和共用體的區別在于:結構體的各個(gè)成員會(huì )占用不同的內存,互相之間沒(méi)有影響;而共用體的所有成員占用同一段內存,修改一個(gè)成員會(huì )影響其余所有成員。
通常使用共用體做一些標志位操作,例如以下示例,可以非常靈活的訪(fǎng)問(wè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;
或者使用共用體實(shí)現單字節與多字節的轉化和拼接,如下所示:
#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=%04Xrn",hello.result);//輸出 result=1234 return 0;}
5
預定義標識符
一般編譯器都支持預定義標識符,這些標識符結合printf等打印信息幫助程序員調試程序是非常有用的,一般編譯器會(huì )自動(dòng)根據用戶(hù)指定完成替換和處理。
常用的預定義標識符如下所示:
__FILE__ //表示編譯的源文件名__LINE__ //表示當前文件的行號__FUNCTION__ //表示函數名__DATE__ //表示編譯日期__TIME__ //表示編譯時(shí)間
在Debug打印日志時(shí)候經(jīng)常會(huì )用到,如下所示:
printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);
6
#與##
#:是一種運算符,用于帶參宏的文本替換,將跟在后面的參數轉成一個(gè)字符串常量。
##:是一種運算符,是將兩個(gè)運算對象連接在一起,也只能出現在帶參宏定義的文本替換中。
#include "stdio.h"#define TO_STR(s) #s#define COMB(str1,str2) str1##str2int main(int argc, char *argv[]){ int UART0= 115200; printf("UART0=%dn", COMB(UART, 0));//字符串合并為變量UART0 printf("%sn", TO_STR(3.14));//將數字變成字符串 return 0;}
7
void 與 void*關(guān)鍵字
void表示的是無(wú)類(lèi)型,不能聲明變量或常量,但是可以把指針定義為void類(lèi)型,如void* ptr。void* 指針可以指向任意類(lèi)型的數據,在C語(yǔ)言指針操作中,任意類(lèi)型的數據地址都可轉為void* 指針。因為指針本質(zhì)上都是unsigned int。
常用的內存塊操作庫函數:
void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);
數據指針為void* 類(lèi)型,對傳入任意類(lèi)型數據的指針都可以操作。另外其中memcpy第二個(gè)參數,const現在也如前文所述,拷貝時(shí)對傳入的原數據內容禁止修改。
特殊說(shuō)明,指針是不能使用sizeof求內容大小的,在A(yíng)RM系統固定為int 4字節。對于函數無(wú)輸入參數的,也盡量加上void,如下所示:
void fun(void);
8
weak關(guān)鍵字
一般簡(jiǎn)化定義如下所示:
#define _WEAK __attribute__((weak))
函數名稱(chēng)前面加上__WEAK屬性修飾符稱(chēng)為“弱函數”,類(lèi)似C++的虛函數。鏈接時(shí)優(yōu)先鏈接為非weak定義的函數,如果找不到則再鏈接帶weak函數。
_WEAK void fun(void) { //do this} //不在同一個(gè).c,兩同名函數不能在同一個(gè)文件void fun(void) { //do that}
這種自動(dòng)選擇的機制,在代碼移植和多模塊配合工作的場(chǎng)景下應用較多。例如前期移植代碼,需要調用某個(gè)接口fun,但當前該接口不存在或者未移植完整使用,可以使用weak關(guān)鍵字定義為空函數先保證編譯正常。
后續移植完成實(shí)現了fun,即軟件中有2個(gè)fun函數沒(méi)有任何錯誤,編譯器自動(dòng)會(huì )識別使用后者。當然也粗暴的#if 0屏蔽對fun的調用,但要確保后續記得放開(kāi)。
*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。