在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

嵌入式C語言開發基礎

玩轉單片機與嵌入式 ? 來源:玩轉單片機與嵌入式 ? 2023-02-16 11:10 ? 次閱讀

1 劍宗氣宗之爭

《笑傲江湖》中華山派的劍宗和氣宗之爭,可謂異常激烈。那么問題就來了,既然有劍宗氣宗之爭,到底應該先練劍,還是先練氣呢?引申到軟件開發行業有沒劍氣之爭呢?

前面發布很多理論方面的文章,諸如4篇:基于RTOS的軟件開發理論嵌入式軟件的設計模式(上)嵌入式軟件的設計模式(下)嵌入式軟件分層隔離的典范這些都是具備一定基礎后在架構上的描述,類似于氣宗性質,這種比較抽象、見效慢。但高質量的軟件開發,也是存在見效快的套路,針對有一定嵌入式C語言開發基礎的,以劍宗之法進行描述,可重點關注if判斷和內存管理相關的講解,拋磚引玉。

2 文件結構

1、C 程序通常分為兩類文件,一種是程序的聲明稱為頭文件,以“.h”為后綴,另一種是程序的實現,以“.c”為后綴,一般每個c文件有個同名的h文件。

2、軟件的頭文件數目比較多,應將頭文件和定義文件分別保存于不同的目錄,例如將頭文件保存于 include或者inc 目錄,將定義文件保存于 source 或src目錄;如果某些頭文件是私有的,它不會被用戶的程序直接引用,則沒有必要公開其“聲明”。為了加強信息隱藏,這些私有的頭文件可以和定義文件存放于同一個目錄,即私有的h文件放在src目錄。

3、在文件頭添加版權和版本的聲明等信息,主要包括版權和功能,以及修改記錄,必要時可以為整個功能文件夾單獨新建readme說明文檔。

4、為了防止頭文件被重復引用,必須用 ifndef/define/endif 結構產生預處理塊。

5、頭文件中只存放“聲明”而不存放“定義”,更別提放變量,這是嚴重的錯誤。

6、用 #include 格式來引用標準庫的頭文件,用 #include “filename.h” 格式來引用非標準庫的頭文件(編譯器將從用戶的工作目錄開始搜索)。

7、文件可按層或者功能組件劃分不同的文件夾,便于其他人閱讀。

3 程序版式

版式雖然不會影響程序的功能,但會影響可讀性,程序的風格統一則是賞心悅目。

代碼排版在編碼時確實很難把握,但可以編碼完成后統一用工具格式化,不管編碼使用Keil/MDK、Qt等集成工具,或者純粹的代碼編輯工具Source Insight,一般都支持自定義運行可執行文件,如Astyle。可以客制化新菜單,一鍵執行Astyle,將代碼一鍵格式化,排版統一、層次分明。

Astyle官網 http://astyle.sourceforge.net/ 按要求下載安裝,只需要AStyle.exe即可。關于其使用和參數,可以再進入Documentation。對代碼基本風格,{}如何對齊、是否換行,switch-case如何排版,tab鍵占位寬度,運算符或變量前后的空格等等,基本上代碼排版涉及的方方面面都有參數說明。個人選擇的編碼參數是

--style=allman-S-U-t-n-K-p-s4-j-q-Y-xW-xVfileName

效果如下

//微信公眾號:嵌入式系統
intFoo(boolisBar)
{
if(isBar)
{
bar();
return1;
}
else
{
return0;
}
}

也可以參考 代碼的保養第3章。關于注釋,重要函數或段落必不可少,修改代碼同時修改相應的注釋,以保證注釋與代碼的一致性。

4 命名規則

比較著名的命名規則當推 Microsoft 公司的“匈牙利”法,該命名規則的主要思想是“在變量和函數名中加入前綴以增進人們對程序的理解”。例如所有的字符變量均以ch 為前綴,若是指針變量則追加前綴 p。但沒有一種命名規則可以讓所有的程序員滿意,制定一種令大多數項目成員滿意的命名規則,重點是在整個團隊和項目中貫徹實施。

事實上開發大多數基于SDK,一般底層命名規則盡量與SDK風格保持一致,至于上層就按團隊標準,個人比較傾向全部小寫字母,用下劃線分割的風格,例如 set_apn、timer_start。

不要出現標識符完全相同的局部變量和全局變量,盡管兩者的作用域不同而不會發生語法錯誤,但會使人誤解,全局變量也不要過于簡短。

變量的名字應當使用“名詞”或者“形容詞+名詞”,函數的名字應當使用“動詞”或者“動詞+名詞”,用正確的反義詞組命名具有互斥意義的變量或相反動作的函數等。

5 基本語句

表達式和語句都屬于C 語法基礎,看似簡單,但使用時隱患比較多,提供一些建議。

5.1 if

if 語句是 C 語言中最簡單、最常用的語句,然而很多程序員卻用隱含錯誤的方式,僅以不同類型的變量與零值比較為例,展開討論。

1、布爾變量與零值比較

不可將布爾變量直接與 TRUE、FALSE 或者 1、0 進行比較。根據布爾類型的語義,零值為“假”(記為 FALSE),任何非零值都是“真”(記為TRUE)。TRUE 的值究竟是什么并沒有統一的標準。

假設布爾變量名字為 flag,它與零值比較的標準 if 語句如下:

//微信公眾號:嵌入式系統
if(flag)//表示flag為真
if(!flag)//表示flag為假

其它的用法都屬于不良風格,例如:

//錯誤范例
if(flag==TRUE)
if(flag==1)
if(flag==FALSE)
if(flag==0)

2、整型變量與零值比較

整型變量用“==”或“!=”直接與 0 比較,假設整型變量的名字為 value,它與零值比較的標準 if 語句如下:

if(value==0)
if(value!=0)

不可模仿布爾變量的風格而寫成

//錯誤范例
if(value)//會讓人誤解value是布爾變量
if(!value)

3、 浮點變量與零值比較

不可將浮點變量用“==”或“!=”與任何數字比較,無論是 float 還是 double 類型的變量,都有精度限制。不能將浮點變量用“==”或“!=”與數字比較,應該設法轉化成“>=”或“<=”形式。假設浮點變量的名字為 x,應當將

if(x==0.0)//隱含錯誤的比較,錯誤

轉化為

constfloatEPSINON=0.00001
if((x>=-EPSINON)&&(x<=EPSINON))?
//其中EPSINON是允許的誤差(即精度),即x無限趨近于0.0

4、指針變量與零值比較

指針變量用“==”或“!=”與 NULL 比較, 指針變量的零值是“空”(記為 NULL),盡管 NULL 的值與 0 相同,但是兩者意義不同。假設指針變量的名字為 p,它與零值比較的標準 if 語句如下:

if(p==NULL)//p與NULL顯式比較,強調p是指針變量
if(p!=NULL)

不要寫成

if(p==0)//容易讓人誤解p是整型變量
if(p!=0)
if(p)//容易讓人誤解p是布爾變量
if(!p)

5.2 for

在多重循環中,如果有可能,應當將最長的循環放在最內層,最短的循環放在最外層,以減少 CPU 切換循環層的次數。

//不良范例
for(row=0;row<100;row++)
{
for(col=0;col<5;col++)
{
sum=sum+a[row][col];
}
}

//微信公眾號:嵌入式系統  較高效率
for(col=0;col<5;col++)
{
for(row=0;row<100;row++)
{
sum=sum+a[row][col];
}
}

5.3 switch

switch 是多分支選擇語句,而 if 語句只有兩個分支可供選擇;雖然可以用嵌套的if 語句來實現多分支選擇,但那樣的程序冗長難讀。這是 switch 語句存在的理由。

switch-case 即使不需要 default 處理,也應該保留語句 default : break; 這樣做并非多此一舉,而是為了防止別人誤以為你忘了 default 處理。確實不需要break的case,務必加上注釋標明。

5.4 goto

很多人建議禁止使用 goto 語句,但實事求是地說,錯誤是程序員自己造成的,不是 goto 的過錯。goto 語句至少有一處可顯神通,它能從多重循環體中一下子跳到外面,特殊場景下可以使用,在很多if嵌套的場景,比如都有同樣的錯誤處理,或者成對操作的文件開關,或者內存申請釋放,就比較適合goto統一處理。

//微信公眾號:嵌入式系統
//代碼只是表意,可能無法編譯
#include

voidtest(void)
{
char*p1,*p2;
p1=(char*)malloc(100);
p1=(char*)malloc(200);

if(0)
{
//dosomething
gotoexit;
}
elseif(0)
{
//dosomething
gotoexit;
}
//dosomething
//...
exit:
free(p1);
free(p2);
}

intmain()
{
goto_test();
return0;
}

對于內存申請釋放、文件打開關閉這種成對操作,或者各種異常處理的統一支持場景,就比較適合goto。類似的還有do-while(0)這種語句。

關于運算優先級,熟記運算符優先級是比較困難的,如果代碼行中的運算符比較多,為了防止產生歧義并提高可讀性,全部加括號明確表達式的操作順序,雖然愚笨但是可靠

6 常量

常量是一種標識符,它的值在運行期間恒定不變。C 語言用 #define 來定義常量(稱為宏常量),但用 const 來定義常量(稱為 const 常量)其實更佳。

#defineMAX100
constfloatPI=3.14159;

const 常量有數據類型,而宏常量沒有數據類型。編譯器可以對前者進行類型安全檢查,而對后者只進行字符替換,沒有類型安全檢查,并且在字符替換可能會產生意料不到的錯誤,所以復雜參數宏必須為每個參數加上()限制。

但也有特例

constintSIZE=100;
intarray[SIZE];//有的編譯器認為是錯誤,這就必須用define了

需要對外公開的常量放在頭文件中,不需要對外公開的常量放在定義文件的頭部。為便于管理,可以把不同模塊的常量集中存放在一個公共的頭文件中。

7函數

函數設計的細微缺點很容易導致該函數被錯用,函數接口的兩個要素是參數和返回值,C 語言中函數的參數和返回值的傳遞方式有值傳遞(pass by value)和指針傳遞(pass by pointer)兩種。

7.1參數的規則

參數的書寫要完整,不要貪圖省事只寫參數的類型而省略參數名字,如果函數沒有參數,則用 void 填充。

voidset_size(intwidth,intheight);//良好的風格
voidset_size(int,int);//不良的風格
intget_size(void);//良好的風格
intget_size();//不良的風格

參數命名要恰當,順序要合理。例如字符串拷貝函數

char*strcpy(char*dest,constchar*src);

從名字上就可以看出應該把 src 拷貝到 dest。還有一個問題,兩個參數哪個該在前哪個該在后?參數的順序要遵循程序員的習慣。一般地,應將目的參數放在前面,源參數放在后面。

這里也說明下const的意義,如果參數僅作輸入用,則應在類型前加 const,以防止在函數體內被意外修改。

避免函數有太多的參數,參數個數盡量控制在 5 個以內,如果參數太多,在使用時容易將參數類型或順序搞錯,可以定為結構體指針,但盡量帶上參數注釋。

除了printf、sprintf標準庫或基于這類的日志輸出接口,盡量不要使用類型和數目不確定的參數。

7.2 返回值的規則

不要省略返回值的類型,默認不加類型說明的函數一律自動按整型處理。為了避免混亂,如果函數沒有返回值,應聲明為 void 類型。

不要將正常值和錯誤標志混在一起返回。正常值用輸出參數獲得,而錯誤標志用 return 語句返回。

7.3 函數內部實現的規則

不同功能的函數其內部實現各不相同,看起來似乎無法就“內部實現”達成一致的觀點。但根據經驗,我們可以在函數體的“入口處”和“出口處”從嚴把關,從而提高函數的質量。

在函數體的“入口處”,對參數的有效性進行檢查,很多程序錯誤是由非法參數引起的,我們應該充分理解并正確使用“斷言”(assert)來防止此類錯誤。

在函數體的“出口處”,對 return 語句的正確性和效率進行檢查。如果函數有返回值,那么函數的“出口處”是 return 語句。調用處應該盡量關注返回值,對異常進行處理

關于return的值,不可返回指向“棧內存”的“指針,該內存在函數體結束時被自動銷毀。例如

char*Func(void)
{
charstr[]=“helloworld”;//str的內存位于棧上returnstr;//將導致錯誤
}

盡量避免函數帶有“記憶”功能,相同的輸入應當產生相同的輸出。帶有“記憶”功能的函數,其行為可能是不可預測的,因為它的行為可能取決于某種“記憶狀態”。這樣的函數既不易理解又不利于測試和維護。在 C語言中,函數的 static 局部變量是函數的“記憶”存儲器。建議盡量少用 static 局部變量,除非必需。

7.4 斷言

程序一般分為 Debug 版本和 Release 版本,Debug 版本用于內部調試,Release 版本發行給用戶使用。斷言 assert 是僅在 Debug 版本起作用的宏,它用于檢查“不應該”發生的情況。在運行過程中,如果 assert 的參數為假,那么程序就會中止。

void*memcpy(void*pvTo,constvoid*pvFrom,size_tsize)
{
assert((pvTo!=NULL)&&(pvFrom!=NULL));//【使用斷言】
byte*pbTo=(byte*)pvTo;//防止改變pvTo的地址
byte*pbFrom=(byte*)pvFrom;//防止改變pvFrom的地址
while(size-->0)
*pbTo++=*pbFrom++;
returnpvTo;
}

assert 不應該產生任何副作用。所以 assert 不是函數,而是宏。可以把assert 看成一個在任何系統狀態下都可以安全使用的無害測試手段。如果程序在 assert處終止了,并不是說含有該 assert 的函數有錯誤,而是調用者出了差錯,assert 有助于找到發生錯誤的原因。

軟件有必要進行防錯設計,如果“不可能發生”的事情的確發生了,則要使用斷言進行報警。

8 內存管理

C語言的內存管理既是它的優勢,也是劣勢。理解它的原理了才能更好的管理內存。

8.1 內存分配方式

內存分配方式有三種:

1、從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static 變量。

2、在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。

3、從堆上分配,亦稱動態內存分配。程序在運行的時候用 malloc 或 new 申請任意多少的內存,程序員自己負責在何時用 free 或 delete 釋放內存。動態內存的生存期由我們決定,使用非常靈活,但風險也大。

8.2 內存錯誤及其對策

發生內存錯誤是件非常麻煩的事情。編譯器不能自動發現這些錯誤,通常是在程序運行時才能捕捉到,而這些錯誤大多沒有明顯的癥狀,時隱時現,增加了改錯的難度。常見的內存錯誤及其對策如下:

1、內存分配未成功,卻使用了它

編程新手常犯這種錯誤,因為他們沒有意識到內存分配會不成功。常用解決辦法是,在使用內存之前檢查指針是否為 NULL。如果指針 p 是函數的參數,可在函數的入口處用 assert(p!=NULL)進行檢查,或者用 if(p==NULL)或 if(p!=NULL)進行防錯處理。

2、內存分配雖然成功,但是尚未初始化就引用它

犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為內存的缺省初值全為零,導致引用初值錯誤。內存的缺省初值究竟是什么并沒有統一的標準(盡管有些時候為零值),為了安全,對分配的內存都進行清零。

3、內存分配成功并且已經初始化,但操作越過了內存的邊界

數組使用時經常會發生下標“多 1”或“少 1”的操作。特別是在 for 循環語句中,循環次數很容易搞錯,導致數組操作越界。

4、忘記釋放內存,造成內存泄露

含有這種錯誤的函數每被調用一次就丟失一塊內存。剛開始時系統的內存充足,運行正常,但隨著運行時間加長,程序突然死掉,內存耗盡。動態內存的申請與釋放必須配對,程序中 malloc 與 free 的成對使用。

5、已經釋放的內存卻繼續使用它

程序中的調用關系過于復雜,邏輯順序錯誤,或者使用了指向“棧內存”的“臨時指針,使用 free 或 delete 釋放了內存后,務必將指針設置為 NULL,使用前判斷是否為NULL。

關于指針的使用建議,用 malloc 申請內存之后,應該立即檢查指針值是否為 NULL,非NULL的賦初值;使用結束后用 free 釋放,且將指針設置為 NULL,防止誤用“野指針”。對動態內存的一些防護性操作,可以參考微信公眾號【嵌入式系統】的文章動態內存管理及防御性編程

8.3 指針與數組的對比

C 程序中指針和數組在不少地方可以相互替換著用,讓人產生一種錯覺,以為兩者是等價的。

數組要么在靜態存儲區被創建(如全局數組),要么在棧上被創建。數組名對應著(而不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數組的內容可以改變。

指針可以隨時指向任意類型的內存塊,它的特征是“可變”,所以我們常用指針來操作動態內存。指針遠比數組靈活,但也更危險。

下面以字符串為例比較指針與數組的特性。

1、修改內容

字符數組 a 的容量是 6 個字符,其內容為 hello。a 的內容可以改變,如 a[0]= ‘X’。指針 p 指向常量字符串“world”(位于靜態存儲區,內容為 world),常量字符串的內容是不可以被修改的。從語法上看,編譯器并不覺得語句 p[0]= ‘X’有什么不妥,但是該語句企圖修改常量字符串的內容而導致運行錯誤。

chara[]=“hello”;
a[0]=‘X’;
cout<endl;
char*p=“world”;//注意p指向常量字符串
p[0]=‘X’;//編譯器不能發現該錯誤
cout<endl;

2、 內容復制與比較

不能對數組名進行直接復制與比較,若想把數組 a 的內容復制給數組 b,不能用語句 b = a ,否則將產生編譯錯誤。應該用標準庫函數 strcpy 進行復制。同理,比較 b 和 a 的內容是否相同,不能用 if(b == a) 來判斷,應該用標準庫函數 strcmp進行比較。

語句 p = a 并不能把 a 的內容復制指針 p,而是把 a 的地址賦給了 p。要想復制 a的內容,可以先用庫函數 malloc 為 p 申請一塊容量為 strlen(a)+1 個字符的內存,再用 strcpy 進行字符串復制。同理,語句 if(p==a) 比較的不是內容而是地址,應該用庫函數 strcmp 來比較。

//數組
chara[]="hello";
charb[10];
strcpy(b,a);//不能用b=a;
if(strcmp(b,a)==0)//不能用if(b==a)

//指針
intlen=strlen(a);
char*p=(char*)malloc(sizeof(char)*(len+1));
strcpy(p,a);//不要用p=a;
if(strcmp(p,a)==0)//不要用if(p==a)

3、計算內存容量

用運算符 sizeof 可以計算出數組的容量(字節數)。sizeof(a)的值是 12(注意別忘了’’)。指針 p 指向 a,但是 sizeof(p)的值卻是 4。這是因為sizeof(p)得到的是一個指針變量的字節數,相當于 sizeof(char*),而不是 p 所指的內存容量。/C 語言沒有辦法知道指針所指的內存容量,只能在申請內存時記住它。

chara[]="helloworld";
char*p=a;
cout<sizeof(a)<endl;//12字節
cout<sizeof(p)<endl;//4字節

當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指針。不論數組 a 的容量是多少,sizeof(a)始終等于 sizeof(char *)。

voidFunc(chara[100])
{
cout<sizeof(a)<endl;//4字節而不是100字節
}

4、指針參數是如何傳遞內存

如果函數的參數是一個指針,不要指望用該指針去申請動態內存。

voidget_memory(char*p,intnum)
{
p=(char*)malloc(sizeof(char)*num);
}
voidtest(void)
{
char*str=NULL;
get_memory(str,100);//str仍然為NULL
strcpy(str,"hello");//運行錯誤
}

test 函數的get_memory(str, 100) 并沒有使 str 獲得期望的內存,str 依舊是 NULL,為什么?

問題出在函數 get_memory,編譯器總是要為函數的每個參數制作臨時副本,指針參數 p 的副本是 _p,編譯器使 _p = p。如果函數體內的程序修改了_p 的內容,就導致參數 p 的內容作相應的修改。這就是指針可以用作輸出參數的原因。而范例中_p 申請了新的內存,只是把_p 所指的內存地址改變了,但是 p 絲毫未變。所以函數 get_memory并不能輸出任何東西。事實上,每執行一次 get_memory就會泄露一塊內存,因為沒有用free 釋放內存。

如果非得要用指針參數去申請內存,那么應該改用“指向指針的指針”,正確范例如下:

voidget_memory2(char**p,intnum)
{
*p=(char*)malloc(sizeof(char)*num);
}
voidtest2(void)
{
char*str=NULL;
get_memory2(&str,100);//注意參數是&str,而不是str
strcpy(str,"hello");
free(str);
}

由于“指向指針的指針”這個概念不容易理解,可以用函數返回值來傳遞動態內存,這種方法更加簡單。

char*get_memory3(intnum)
{
char*p=(char*)malloc(sizeof(char)*num);
returnp;
}
voidtest3(void)
{
char*str=NULL;
str=get_memory3(100);
//建議增加str指針是否為NULL判斷,并清零內容
strcpy(str,"hello");
free(str);
}

用函數返回值來傳遞動態內存這種方法雖然好用,但是常常有人把 return 語句用錯,不要用 return 語句返回指向“棧內存”的指針,因為該內存在函數結束時自動消亡,錯誤范例如下:

//錯誤范例
char*get_string(void)
{
charp[]="helloworld";
returnp;//編譯器將提出警告
}
voidtest4(void)
{
char*str=NULL;
str=get_string();//str的內容是隨機垃圾
}

執行str = get_string()后 str 不再是 NULL 指針,但是 str 的內容不是“hello world”而是垃圾。

char*get_string2(void)
{
char*p="helloworld";
returnp;
}
voidtest5(void)
{
char*str=NULL;
str=get_string2();
}

函數 test5 運行雖然不會出錯,但是函數 get_string2的設計概念卻是錯誤的。因為 get_string2內的“hello world”是常量字符串,位于靜態存儲區,它在程序生命期內恒定不變。無論什么時候調用 get_string2,它返回的始終是同一個“只讀”的內存塊,也就是test5是無法修改str的。

5、 free 把指針怎么了

free 只是把指針所指的內存給釋放掉,但并沒有把指針本身干掉;指針 p 被 free 以后其地址仍然不變(非 NULL),只是該地址對應的內存是垃圾,p 成了“野指針”。如果此時不把 p 設置為 NULL,會讓人誤以為 p 是個合法的指針。

如果程序比較長,我們有時記不住 p 所指的內存是否已經被釋放,在繼續使用 p 之前,通常會用語句 if (p != NULL)進行防錯處理。很遺憾,此時 if 語句起不到防錯作用,此時 p 不是 NULL 指針,但它也不指向合法的內存塊。

char*p=(char*)malloc(100);
strcpy(p,“hello”);
free(p);//p所指的內存被釋放,但是p所指的地址仍然不變

if(p!=NULL)//沒有起到防錯作用
{
strcpy(p,“world”);//出錯
}

6、動態內存會被自動釋放嗎

函數體內的局部變量在函數結束時自動消亡。

voidfunc(void)
{
char*p=(char*)malloc(100);//動態內存會自動釋放嗎?
}

但是,變量p 是局部的指針變量,它消亡的時候并不會讓它所指的動態內存一起完蛋。發現指針有一些“似是而非”的特征:

(1)指針消亡了,并不表示它所指的內存會被自動釋放。

(2)內存被釋放了,并不表示指針會消亡或者成了 NULL 指針。

7、杜絕“野指針”

“野指針”不是 NULL 指針,是指向“垃圾”內存的指針。人們一般不會錯用 NULL指針,因為用 if 語句很容易判斷;但是“野指針”是很危險的,if 語句對它不起作用。“野指針”的成因主要有三種:

(1)指針變量沒有被初始化。任何指針變量剛被創建時不會自動成為 NULL 指針,它的缺省值是隨機的,所以,指針變量在創建的同時應當被初始化。

(2)指針 p 被 free 或者 delete 之后,沒有置為 NULL,讓人誤以為 p 是個合法的指針。

(3)指針操作超越了變量的作用范圍。這種情況讓人防不勝防。

8、內存耗盡怎么辦

如果在申請動態內存時找不到足夠大的內存塊,malloc 將返回 NULL 指針,宣告內存申請失敗。判斷指針是否為 NULL,如果是則馬上用 return 語句終止本函數,或者用 exit(1)終止整個程序的運行。如果發生“內存耗盡”,一般說來應用程序已經無藥可救,嵌入式設備只能重啟了。

9、心得體會

很少有人能拍拍胸脯說通曉指針與內存管理,越是怕指針,就越要使用指針。不會正確使用指針,肯定算不上是合格的嵌入式程序員。

9 其它編程經驗

9.1 使用 const 提高函數的健壯性

const 是 constant 的縮寫,“恒定不變”的意思。被 const 修飾的東西都受到強制保護,可以預防意外的變動,能提高程序的健壯性。很多 C++程序設計書籍建議:“Use const whenever you need”。

1、用 const 修飾函數的參數如果參數作輸出用,不論它是什么數據類型,都不能加 const 修飾,否則該參數將失去輸出功能。const 只能修飾輸入參數,如果輸入參數采用“指針傳遞”,那么加 const 修飾可以防止意外地改動該指針,起到保護作用。例如 strcpy函數:

char*strcpy(char*dest,constchar*src);

其中 src是輸入參數,dest是輸出參數。給 src加上 const修飾后,如果函數體內的語句試圖改動 src 的內容,編譯器將指出錯誤。

2、如果輸入參數采用“值傳遞”,由于函數將自動產生臨時變量用于復制該參數,該輸入參數本來就無需保護,所以不要加 const 修飾。

voidfunc1(intx)寫成voidfunc1(constintx)//const無意義

3、對于非內部數據類型的參數而言,如 void func(A a) 這樣聲明的函數注定效率比較低,其中 A 為用戶自定義的數據類型,可以理解為大結構。

函數體內將產生 A 類型的臨時對象用于復制參數 a,而臨時對象的構造、復制、析構過程都將消耗時間。為了提高效率,可以將函數聲明改為:

voidfunc(A&a)

因為“引用傳遞”僅借用一下參數的別名而已,不需要產生臨時對象。但是函數 存在一個缺點,“引用傳遞”有可能改變參數 a,這是我們不期望的。解決這個問題很容易,加 const修飾即可,因此函數最終成為

voidfunc(constA&a)

4、用 const 修飾函數的返回值,如果給以“指針傳遞”方式的函數返回值加 const 修飾,那么函數返回值(即指針)的內容不能被修改,該返回值只能被賦給加 const 修飾的同類型指針。例如函數

constchar*get_string(void);
char*str=get_string();//出現編譯錯誤:
constchar*str=get_string();//正確的用法

9.2 提高程序的效率

程序的時間效率是指運行速度,空間效率是指程序占用內存或者外存的狀況。

不要一味地追求程序的效率,應當在滿足正確性、可靠性、健壯性、可讀性等質量因素的前提下,設法提高程序的效率。

在優化程序的效率時,應當先找出限制效率的“瓶頸”,不要在無關緊要之處優化。有時候時間效率和空間效率可能對立,此時應當分析那個更重要,作出適當的折衷。例如多花費一些內存來提高性能。

關于其它C關鍵字用法,可以參考C語言關鍵字應用技巧

10 小結

不論劍宗、氣宗優劣,先把功能跑通再反推代碼原理和實現流程,還是先理清時序和原理再編碼實現功能,短期內劍宗效率高,加工資快,但后期發展有限;氣宗則面臨前期可能被淘汰,尤其在勢利的小公司,不注重新人培養,但前期積累,后期融會貫通,在技術方面成為權威。如果合二為一,項目緊急則拿來就用,空閑時專研總結,取長補短,則是高級程序員的素質。

審核編輯 :李倩


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • C語言
    +關注

    關注

    180

    文章

    7608

    瀏覽量

    137119
  • RTOS
    +關注

    關注

    22

    文章

    817

    瀏覽量

    119717
  • 代碼
    +關注

    關注

    30

    文章

    4802

    瀏覽量

    68738

原文標題:10 小結

文章出處:【微信號:玩轉單片機與嵌入式,微信公眾號:玩轉單片機與嵌入式】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    嵌入式C語言開發教程之用c寫CGI_程序簡要指南

    嵌入式C語言開發教程之用c寫CGI_程序簡要指南
    發表于 08-20 09:55

    陳正沖—C語言深度解剖(第2版)(電子版)

    ,絕對受益匪淺。 嵌入式C語言開發經驗和平時講解C語言的心得體會整理而成,其中有很多作者獨特的見
    發表于 12-26 13:20

    嵌入式C語言開發嵌入式Linux C開發的區別

    嵌入式Linux系統開發嵌入式Linux系統開發(應用軟件開發):通過內核提供的服務實現相應功能一、嵌入
    發表于 11-05 08:12

    數據變量的相關資料推薦

    嵌入式C語言開發入門——數據變量變量的四個部分:空間、變量名、變量地址、變量類型C語言標識符命名
    發表于 12-15 07:19

    測試驅動的嵌入式C語言開發讀書筆記分享

    測試驅動的嵌入式C語言開發讀書筆記1.測試驅動開發瀑布模型的最后開發人員會亂作一團,而縮短
    發表于 12-15 07:25

    C語言的編譯步驟

    嵌入式C語言開發入門——程序編譯計算機語言發展過程C語言
    發表于 12-15 08:21

    嵌入式C 語言開發ADSP21XX 系列DSP

    詳細介紹使用VisualDSP 開發工具進行ADSP21XX 的C 語言編程的方法;分析其C 語言運行庫的結構,并且結合實例介紹
    發表于 05-15 14:42 ?21次下載

    基于C語言嵌入式軟件開發中的錯誤追蹤機制

      引言   本文針對嵌入式C語言開發的特點,提出一種基于堆棧模式的異常追蹤編程模型,能夠實現有效的異常現場保存與恢復,并為后期的問題分析與解決打好基礎。
    發表于 08-19 09:25 ?748次閱讀
    基于<b class='flag-5'>C</b><b class='flag-5'>語言</b>的<b class='flag-5'>嵌入式</b>軟件<b class='flag-5'>開發</b>中的錯誤追蹤機制

    嵌入式c語言編程(由淺入深)

    本內容詳細介紹了嵌入式c語言編程的各項知識,包括嵌入式c語言編程,
    發表于 11-02 14:37 ?0次下載
    <b class='flag-5'>嵌入式</b><b class='flag-5'>c</b><b class='flag-5'>語言</b>編程(由淺入深)

    C語言深度解剖完美PDF電子書免費下載

    C語言深度解剖》是2012年出版的圖書,作者是陳正沖。本書作者結合自身多年嵌入式C語言開發經驗
    發表于 11-28 15:35 ?23次下載

    基于嵌入式C語言開發中的異常堆棧錯誤追蹤機制的設計

    對于嵌入式軟件來說,盡量節省內存資源、降低程序代碼量是十分重要的。因此,將程序中所有錯誤、異常情況都進行了統一編碼,提高了錯誤處理代碼的規范化與可讀性。設計8位整數編碼格式如下:
    發表于 03-09 10:35 ?1224次閱讀
    基于<b class='flag-5'>嵌入式</b><b class='flag-5'>C</b><b class='flag-5'>語言</b><b class='flag-5'>開發</b>中的異常堆棧錯誤追蹤機制的設計

    汽車電子行業的MISRA C標準分享

    的、高可靠性的嵌入式軟件。MISRA C則是由MISRA提出的針對嵌入式C語言開發標準,目的是提
    的頭像 發表于 05-11 13:43 ?1913次閱讀

    嵌入式linux c語言,嵌入式LinuxC語言開發工具.pdf

    2 章 嵌入式Linux C 語言開發工具本章目標任何應用程序的開發都離不開編輯器、編譯器及調試器,嵌入
    發表于 11-01 17:38 ?12次下載
    <b class='flag-5'>嵌入式</b>linux <b class='flag-5'>c</b><b class='flag-5'>語言</b>,<b class='flag-5'>嵌入式</b>LinuxC<b class='flag-5'>語言</b><b class='flag-5'>開發</b>工具.pdf

    嵌入式系統設計--課堂總結(嵌入式Linux系統開發

    嵌入式Linux系統開發嵌入式Linux系統開發(應用軟件開發):通過內核提供的服務實現相應功能一、嵌入
    發表于 11-02 12:21 ?21次下載
    <b class='flag-5'>嵌入式</b>系統設計--課堂總結(<b class='flag-5'>嵌入式</b>Linux系統<b class='flag-5'>開發</b>)

    嵌入式軟件開發工程師要求

    年終獎金績效獎金定期體檢申請職位競爭力分析收藏職位信息1、2年以上嵌入式C語言開發經驗或2年以上車身電子產品開發經驗2、有良好的英語或日語文
    發表于 11-03 12:06 ?3次下載
    <b class='flag-5'>嵌入式</b>軟件<b class='flag-5'>開發</b>工程師要求
    主站蜘蛛池模板: 69日本xxxxhd| 亚洲色图17p| 欧美成人午夜精品免费福利| 免费观看色| 久久久久久久综合色一本| 国产美女特级嫩嫩嫩bbb| sesese在线观看| 闲人综合| 久久精品国产乱子伦多人| 91一级片| 青娱乐啪啪| 男人的天堂天堂网| 国产成人小视频| 午夜欧美精品| 国产理论视频在线观看| 中文字幕在线观看一区二区| www.亚洲成人| 色丁香影院| 国产在线五月综合婷婷| 久青草视频在线| 国产亚洲第一伦理第一区| 久久成人亚洲| 在线观看永久免费| 久久久久久久成人午夜精品福利| 亚洲操综合| 人色网| jlzzjlzz亚洲日本| 黄色网在线| 曰韩欧美| 噜噜噜噜天天狠狠| 一级黄色片a| 天天做天天爱天天射| 免费一级e一片在线播放| 美女被免费网站91色| www.91大神| 双性受粗大撑开白浊| 性生生活三级视频在线观看| 两性色午夜视频自由成熟的性| 亚洲第一视频网| 六月激情丁香| 四虎影院欧美|