0 規范制定說明
0.1 箴言
技術人員設計程序的首要目的是用于技術人員溝通和交流,其次才是用于機器執行。程序的生命力在于用戶使用,程序的成長在于后期的維護及根據用戶需求更新和升級功能。
如果你的程序只能由你來維護,當你離開這個程序時,你的程序也和你一起離開了,這將給公司和后來接手的技術人員帶來巨大的痛苦和損失。
因此,為了程序可讀、易理解、好維護,你的程序需要遵守一定的規范,你的程序需要設計。
“程序必須為閱讀它的人而編寫,只是順便用于機器執行。”
—— Harold Abelson 和 Gerald Jay Sussman
“編寫程序應該以人為本,計算機第二。”
—— Steve McConnell
0.1 簡介
為提高產品代碼質量,指導儀表嵌入式軟件開發人員編寫出簡潔、可維護、可靠、可測試、高效、可移植的代碼,編寫了本規范。
本規范將分為完整版和精簡版,完整版將包括更多的樣例、規范的解釋以及參考材料(what & why),而精簡版將只包含規則部分(what)以便查閱。
在本規范的最后,列出了一些業界比較優秀的編程規范,作為延伸閱讀參考材料。
本規范主要包含以下兩個方面的內容:
一:為形成統一編程規范,從編碼形式角度出發,本規范對標示符命名、格式與排版、注釋等方面進行了詳細闡述。
二:為編寫出高質量嵌入式軟件,從嵌入式軟件安全及可靠性出發,本規范對由于C語言標準、C語言本身、C編譯器及個人理解導致的潛在危險進行說明及規避。
0.3 適用范圍
本規范適用于XXX股份有限公司儀表臺秤產品部嵌入式軟件的開發,也對其他嵌入式軟件開發起一定的指導作用。
0.4 術語定義
0.4.1規范術語
原則:編程時必須堅持的指導思想。
規則:編程時需要遵循的約定,分為強制和建議(強制是必須遵守的,建議是一般情況下需要遵守,但沒有強制性)。
說明:對原則/規則進行必要的解釋。
實例:對此原則/規則從正、反兩個方面給出例子。
材料:擴展、延伸的閱讀材料。
Unspecified:未詳細說明的行為,這些是必須成功編譯的語言結構,但關于結構的行為,編譯器的編寫者有某些自由。例如C語言中的“運算次序”問題。這樣的問題有 22 個。
在某種方式上完全相信編譯器的行為是不明智的。編譯器的行為甚至不會在所有可能的結構中都是一致的。
Undefined:未定義行為,這些是本質的編程錯誤,但編譯器的編寫者不一定為此給出錯誤信息。相應的例子是無效參數傳遞給函數,或函數的參數與定義時的參數不匹配。從安全性角度這是特別重要的問題,因為它們代表了那些不一定能被編譯器捕捉到的錯誤。
Implementation-defined:實現定義的行為,這有些類似于“unspecified ”問題,其主要區別在于編譯器要提供一致的行為并記錄成文檔。換句話說,不同的編譯器之間功能可能會有不同,使得代碼不具有可移植性,但在任一編譯器內,行為應當是良好定義的。
比如用在一個正整數和一個負整數上的整除運算“/ ”和求模運算符“% ”。存在76個這樣的問題。
從安全性角度,假如編譯器完全地記錄了它的方法并堅持它的實現,那么它可能不是那樣至關重要。盡可能的情況下要避免這些問題。
0.4.2 C語言相關術語
聲明(declaration):指定了一個變量的標識符,用來描述變量的類型,是類型還是對象,函數等。聲明,用于編譯器(compiler)識別變量名所引用的實體。以下這些就是聲明:
externintbar; externintg(int,int); doublef(int,double);[ 對于函數聲明,extern關鍵字是可以省略的 。]
定義(definition):是對聲明的實現或者實例化。連接器(linker)需要它(定義)來引用內存實體。
與上面的聲明相應的定義如下:
intbar; intg(intlhs,intrhs) { returnlhs*rhs; } doublef(inti,doubled){ returni+d; }
0.5 規則的形式
規則/原則<序號>(規則類型):規則內容。
[原始參考]
<序號>:每條規則都有一個序號,序號是按照章節目錄-**的形式,從數字1開始。例如,若在此章節有個規則的話,序號為0.5-1。
(規則類型):或者是‘強制’,或者是‘建議’。
規則內容:此條規則的具體內容。
[原始參考]:指示了產生本條款或本組條款的可應用的主要來源。
1 標示符命名規則
1.1 標示符命名總則
規則1.1-1(強制):標識符(內部的和外部的)的有效字符不能多于31。
[UndefinedImplementation-defined]
說明:ISO 標準要求在內部標識符之間前31 個字符必須是不同的,外部標識符之間前6 個字符必須是不同的(忽略大小寫)以保證可移植性。我們這里放寬了此要求,要求內部、外部標示符的有效字符不能多于31即可。
這樣主要是便于編譯器識別,代碼清晰易讀,并保證可移植性。
規則1.1-2(強制):具有內部作用域的標識符不應使用與具有外部作用域的標識符相同的名稱,在內部作用域里具有內部標示符會隱藏外部標識符。
說明:外部作用域和內部作用域的定義如下。文件范圍內的標識符可以看做是具有最外部(outermost )的作用域;塊范圍內的標識符看做是具有更內部(more inner)的作用域,連續嵌套的塊,其作用域更深入。如果內部作用域標示符和外部作用域標示符同名,內部作用域標示符會覆蓋外部作用域標示符,導致程序混亂。
實例:
INT8Utest; { INT8Utest;/*定義了兩個test*/ test=3;/*這將產生混淆*/ }
規則1.1-3(建議):具有靜態存儲期的對象或函數標識符不能重用。
說明:不管作用域如何,具有靜態存儲期的標識符都不應在系統內的所有源文件中重用。它包含帶有外部鏈接的對象或函數,及帶有靜態存儲類標識符的任何對象或函數。
在一個文件中存在一個具有內部鏈接的標識符,而在另外一個文件中存在著具有外部鏈接的相同名字的標識符,或者存在兩個標示符相同的外部標示符。對用戶來說,這有可能導致混淆。
實例:
test1.c
/**定義了一個靜態文件域變量test1*/ staticINT8Utest1; voidtest_fun(void) { INT8Utest1;/*定義了一個同名的局部變量test1*/ }
test2.c
/**在另一個文件又定義了一個具有外部鏈接的文件域變量test1*/ INT8Utest1;
原則1.1-4(強制):標識符的命名要清晰、明了,有明確含義,同時使用完整的單詞或大家基本可以理解的縮寫,避免使人產生誤解。
說明:標示符的命名盡量做到見名知意,盡量讓別人快速理解你的代碼。
實例:
好的命名方法:
INT8U debug_message ;
INT16U err_num ;
不好的命名方法:
INT8U dbmesg ;
INT16U en ;
原則1.1-5(強制):常見通用的單詞縮寫盡量統一,不得使用漢語拼音、英語混用。
說明:簡短的單詞可以使用略去‘元音’字母形成縮寫,較長的單詞可以使用音節首字母單詞前幾個字母形成縮寫,針對大家公認的單詞縮寫要統一。對于特定的項目要使用的專有縮寫應該注明或者做統一說明。
實例:
常見單詞縮寫表(建議):
單詞 | 縮寫 | 單詞 | 縮寫 |
argument | arg | buffer | buf |
clock | clk | command | cmd |
compare | cmp | configuration | cfg |
device | dev | error | err |
hexadecimal | hex | increment | inc |
initialize | init | maximum | max |
message | msg | minimum | min |
parameter | param | previous | prev |
register | reg | semaphore | sem |
statistic | stat | synchronize | syn |
temp | tmp |
原則1.1-6(建議):用正確的反義詞組命名具有互斥意義的變量或相反動作的函數等。
實例:常見反義詞表:
正義 | 反義 | 正義 | 反義 |
add | remove | begin | end |
create | destroy | insert | delete |
first | last | get | release |
increment | decrement | put | get |
add | delete | lock | unlock |
open | close | min | max |
old | new | start | stop |
next | previous | source | target |
show | hide | send | receive |
source | destination | copy | pase |
up | down |
原則1.1-7(建議):標示符盡量避免使用數字編號,除非邏輯上需要。
實例:
#defineDEBUG_0_MSG #defineDEBUG_1_MSG 應改為更有意義的定義: #defineDEBUG_WARN_MSG #defineDEBUG_ERR_MSG
參考材料:《代碼大全第2版》(Steve McConnell 著 金戈/湯凌/陳碩/張菲 譯 電子工業出版社2006年3月)"第11章變量命的力量"。
1.2 文件命名及存儲規則
規則1.2-1(強制):文件名使用小寫字母。
說明:由于不同系統對文件名大小寫處理不同,Windows不區分文件名大小寫,而Linux區分。所以文件名命名均采用小寫字母,多個單詞之間可使用”_”分隔符。
實例:disp.h os_sem.c
規則1.2-2(建議):工程源碼使用GB2312編碼方式。
說明:程序里的注釋可能會使用中文,GB2312是簡體中文編碼,大部分的編輯工具和集成IDE環境都支持GB2312編碼,為避免中文亂碼,建議使用GB2312對源碼進行編碼。若需要轉換成其他編碼格式,可使用文本編碼轉換工具進行轉換。
規則1.2-3(強制):工程源碼使用版本管理工具進行版本管理。
說明:程序一般需要大量更新、修正、維護工作,且有時需要多人合作。使用版本管理工具可以幫助你提高工作效率。建議使用“Git”版本管理工具。
1.3 變量命名規則
原則1.3-1(強制):變量命名應明確所代表的含義或者狀態。
說明:變量名稱可以使用名詞表述清楚的盡量使用名詞,使用名詞無法描述清楚時,使用形容詞或者描述性的單詞+名詞的形式。變量一般為實體的屬性、狀態等信息,使用上述方案一般可以解決變量名的命名問題,如果出現命名很困難或者無法給出合理的命名方式時,問題可能出現在整體設計上,請重新審視設計。
規則1.3-2(強制):全局變量添加”G_”前綴,全局靜態變量添加” S_ ”,局部靜態變量添加”s_”前綴。使用大小寫混合方式命名,大寫字母用于分割不同單詞。
說明:添加前綴的原因有兩個。首先,使全局變量變得更醒目,提醒技術開發人員使用這些變量時要小心。其次,添加前綴使全局變量和靜態變量變得和其他變量不一致,提醒技術開發人員盡量少用全局變量。
實例:
/**出錯信息*/ INT8UG_ErrMsg; /**每秒鐘轉動圈數*/ staticINT32US_CirclePerSec;
規則1.3-3(強制):局部變量使用小寫字母,若標示符比較復雜,使用’_’分隔符。
說明:局部變量全部使用小寫字母,和全局變量有明顯區分,使讀者看到標示符就知道是何種作用域的變量。
實例:
INT32Udownload_program_address;
規則1.3-4(強制):定義指針變量*緊挨變量名,全局指針變量使用大寫P前綴”P_”,局部指針變量使用小寫p前綴”p _”。
實例:
INT8U*P_MsgAddress;/*全局變量*/ INT8U*p_msg;/*局部變量*/
1.4 函數命名規則
原則1.4-1(強制):函數命名應該明確針對什么對象做出了什么操作。
說明:函數的功能是獲取、修改實體的屬性、狀態等,采用“動詞+名詞”的方式可以滿足上述需求,若出現使用此方式命名函數很困難或不能命名的情況,問題可能出現在整體設計上,請重新審視設計方案。
規則1.4-2(強制):具有外部鏈接的函數命名使用大小寫混合的方式,首字母大寫,用于分割不同單詞。
說明:函數具有外部鏈接屬性的含義是函數通過頭文件對外聲明后,對其他文件或模塊來說是可見的。如果一個函數要在其他模塊或者文件中使用,需要在頭文件中聲明該函數。另外,在頭文件聲明函數,還可以促使編譯器檢查函數聲明和調用的一致性。
實例:
char*GetErrMsg(ErrMsg*msg);
規則1.4-3(強制):具有文件內部鏈接屬性的函數命名使用小寫字母,使用’_’分隔符分割不同單詞,且使用static關鍵字限制函數作用域。
說明:函數具有內部鏈接屬性的含義是函數只能在模塊或文件內部調用,對文件或模塊外來說是不可見的。如果一個函數僅在模塊內部或者文件內部使用,需要限制函數使用范圍,使用static修飾符修飾函數,使其只具有內部鏈接屬性。
在源文件中聲明一遍具有內部鏈接的函數同樣具有促使編譯器檢查函數聲明和調用的一致性。
實例:
staticcharget_key(void);
規則1.4-4(強制):函數參數使用小寫字母,各單詞之間使用“_”分割,盡量保持參數順序從左到右為:輸入、修改、輸出。
說明:函數參數順序為需輸入參數值(這個值一般不修改,若不需要修改使用const關鍵字修飾),需修改的參數(這個參數輸入后用于提供數據,函數內部可以修改此參數),輸出參數(這個參數是函數輸出值)。
1.5 常量的命名規則
規則1.5-1(強制):常量(#define定義的常量、枚舉、const定義的常量)的定義使用全大寫字母,單詞之間加 ’_’分割的命名方式。
實例:
#definePI_ROUNDED3.14 constdoublePI_ROUNDED=3.14; enumweekday{SUN,MON,TUE,WED,THU,FRI,SAT};
規則1.5-2(建議):常數宏定義時,十六進制數的表示方法為0xFF。
說明:前面0x中的x小寫,數據中的”A-F”大寫。
1.6 新定義的類型命名規范
規則1.6-1(強制):新定義類型名的命名應該明確抽象對象的含義,新類型名使用大寫字母,單詞之間加’_’分割,新類型指針在類型名前增加前綴”P_”。
成員變量標示符前加類型名稱前綴,首字母大寫用于區分各個單詞。
實例:
typedefstruct_STUDENT { StudentName; StudentAge; ...... }STUDENT,*P_STUDENT; /*STUDENT為新類型名稱,P_STUDENT為新類型指針名*/
2 外觀布局
2.1 排版與格式
2.1.1 頭文件排版
規則2.1.1-1(強制):頭文件排版內容依次為包含的頭文件、宏定義、類型定義、聲明變量、聲明函數。且各個種類的內容間空三行。
說明:頭文件是模塊對外的公用接口。在頭文件中定義的宏,可以被其他模塊引用。Project中不建議使用全部變量,若使用則需在頭文件里對外聲明。模塊對外的函數接口在模塊頭文件里聲明。
2.1.2 源文件排版
規則2.1.2-1(強制):源文件排版內容依次為包含的頭文件、宏定義、具有外部鏈接屬性的全局變量定義、模塊內部使用的static變量、具有內部鏈接的函數聲明、函數實現代碼。且各個種類的內容間空三行。
說明:模塊內部定義的宏,只能在該模塊內部使用。只在模塊內部使用的函數,需在源碼文件中聲明,用于促使編譯器檢查函數聲明和調用的一致性。
規則2.1.2-2(強制):程序塊采用縮進風格編寫,每級縮進4個空格。
說明:當前主流IDE都支持Tab縮進,使用Tab縮進需要打開和設置相關選項。宏定義、編譯開關、條件預處理語句可以頂格。
規則2.1.2-3(強制):if、for、do、while、case、switch、defaul、typedef等語句獨占一行,且這些關鍵字后需空一格。
說明:執行語句必須用縮進風格寫,屬于if、for、do、while、case、switch、default、typedef等的下一個縮進級別。一般寫if、for、do、while等語句都會有成對出現的{}?,if、for、do、while等語句后的執行語句建議增加成對的“{}”;如果if/else語句塊中只有一條語句,也需增加“{}”。
實例:
for(i=0;i?max_num;?i++) { for??(j??=??0;??j???max_num;?j++) ????{ ????????If?(name_found) ??????????????????{ ???????????????????????????語句 ?????} else ????{ ??????????????語句 ????} ?} }
規則2.1.2-4(強制):進行雙目運算、賦值時,操作符之前、之后要加空格;進行非對等操作時,如果是關系密切的立即操作符(如->),后不應加空格。
說明:采用這種方式書寫代碼,主要目的是使代碼更清晰,使關鍵操作符更突出。
實例:
(1)比較操作符, 賦值操作符"="、 "+=",算術操作符"+"、"%",邏輯操作符"&&"、"&",位域操作符"<<"、"^"等雙目操作符的前后加空格。
If(a>b) a+=2; b=a^3;
(2)"!"、"~"、"++"、"--"、"&"(地址操作符)等單目操作符前后不加空格。
Search_dowm=!true; a++;
(3)"->"、"."、”[]”前后不加空格。
Weight=G_Car->weight; eye=People.eye; array[8]=8;
規則2.1.2-5(建議):一行只定義一個變量,一行只書寫一條執行語句,多行同類操作時操作符盡量保持對齊。
說明:一行定義一個變量,一行只書寫一條執行語句,方便注釋,多行同類操作對齊美觀、整潔。
實例:
events_rdy=OS_FALSE; events_rdy_nbr=0; events_stat=OS_STAT_RDY; pevents=pevents_pend; pevent=*pevents;
規則2.1.2-6(建議):函數內部局部變量定義和函數語句之間應空三行。
說明:局部變量定義和函數語句是相對獨立的,而且空三行可以更清晰地表示出這種獨立性。
3 注釋
3.1 注釋原則
原則3.1-1(強制):注釋的內容要清楚、明了,含義準確,在代碼的功能、意圖層次上進行注釋。
說明:注釋的目的是讓讀者快速理解代碼的意圖。注釋不是為了名詞解釋(what),而是說明用途(why)。
實例:
如下注釋純屬多余:
++i;//i增加1 if(data_ready)/*如果data_ready為真*/
如下注釋無任何參考價值:
// 時間有限,現在是:04,根本來不及想為什么,也沒人能幫我說清楚
原則3.1-2(強制):注釋應分為兩個角度進行,首先是應用角度,主要是告訴使用者如何使用接口(即你提供的函數),其次是實現角度,主要是告訴后期升級、維護的技術人員實現的原理和細節。
說明:每一個產品都可以分為三個層次,產品本身是一個層次,這個層次之下的是你使用的更小的組件,這個層次之上的是你為別人提供的服務。你這個產品的存在的價值就在于把最底層的小部件的使用細節隱藏,同時給最上層的用戶提供方便、簡潔的
使用接口,滿足需求。
從這個角度來看軟件的注釋,你應該時刻想著你寫的注釋是給那一層次的人員看的,如果是用戶,那么你應該注重描述如何使用,如果是后期維護者,那么你應該注重原理和實現細節。
原則3.1-3(強制):修改代碼時,應維護代碼周邊的注釋,使其代碼和注釋一致,不再使用的注釋應刪除。
說明:注釋的目的在于幫助讀者快速理解代碼使用方法或者實現細節,若注釋和代碼不一致會起到相反的作用。建議在修改代碼前應該先修改注釋。
規則3.1-4(建議):代碼段不應被“注釋掉”(comment out )。
說明:當源代碼段不需要被編譯時,應該使用條件編譯來完成(如帶有注釋的#if或#ifdef 結構)。為這種目的使用注釋的開始和結束標記是危險的,因為C 不支持/**/嵌套的注釋,而且已經存在于代碼段中的任何注釋將影響執行的結果。
3.2 文件注釋
規則3.2-1(強制):文件注釋需放到文件開頭,具體格式見實例。
實例:
stm32f10x_dac.h /** ****************************************************************************** *@filestm32f10x_dac.h *@briefThisfilecontainsallthefunctionsprototypesfortheDACfirmware *library. *@authorMCDApplicationTeam *@versionV3.5.0 *@date11-March-2014 *@parModification:添加函數,支持********
*History *Version:V3.0.1
*Author:***
*Modification:添加函數,支持********
*Version:V3.0.0
*Author:***
*Modification:添加函數,支持********
************************************************************************* *@attention ********************************************************* */
說明:注釋格式可被doxygen工具識別,其中@file、@brief、@author等是doxygen工具識別的關鍵字,注釋內容可以為中文。
3.3 函數注釋
規則3.3-1(強制):函數注釋分為頭文件中函數原型聲明時的注釋和源文件中函數實現時的注釋。頭文件中的注釋注重函數使用方法和注意事項,源文件中的注釋注重函數實現原理和方法。具體格式見實例。
說明:函數原型聲明的注釋按照doxygen工具可以識別的格式進行注釋,用于doxygen工具生成頭文件信息以及函數間的調用關系信息。
源代碼實現主要是注釋函數實現原理及修改記錄,不需按照doxygen工具要求的注釋格式進行注釋。
實例:
頭文件函數原型聲明注釋:
/** ******************************************************************** *@briefConfiguresthediscontinuousmodefortheselectedADCregular *groupchannel. *@paramADCx:wherexcanbe1,2or3toselecttheADCperipheral. *@paramNumber:specifiesthediscontinuousmoderegularchannel *countvalue.Thisnumbermustbebetween1and8. *@retvalNone *@par Usage: *ADC_DiscModeChannelCountConfig(ADC1,6);
*@parTag: *此函數不能在中斷里調用。 ******************************************************************** */ voidADC_DiscModeChannelCountConfig(ADC_TypeDef*ADCx,INT8U_tNumber);
源文件函數實現注釋:
/* ******************************************************************** *@briefConfiguresthediscontinuousmodefortheselectedADCregular *groupchannel. *@paramADCx:wherexcanbe1,2or3toselecttheADCperipheral. *@paramNumber:specifiesthediscontinuousmoderegularchannel *countvalue.Thisnumbermustbebetween1and8. *@retvalNone *@parModification:修改了********
*History * Modified by:***
*Date:2013-10-10 *Modification:修改了********
******************************************************************** */ voidADC_DiscModeChannelCountConfig(ADC_TypeDef*ADCx,INT8U_tNumber) { 賦值語句*********;/*關鍵語句的注釋*/ 語句***********;/*關鍵語句的注釋格式*/ 語句*******;/*實現*****************功能*/ }
3.4 常量及全局變量注釋
規則3.3-1(強制):常量、全局變量需要注釋,注釋格式見實例。
實例:
/**Descriptionofthemacro*/ #defineXXXX_XXX_XX0 /**Descriptionofglobalvariable*/ INT8UG_xxx=0;
說明:若全局變量在.c文件中定義,又在.h文件中聲明,則在頭文件中使用doxygen
格式注釋,在源碼文件中使用 /* Description of the globalvariable */的形式。
防止doxygen生成兩遍注釋文檔信息。
3.5 局部變量及語句注釋
規則3.3-1(強制):局部變量,函數實現關鍵語句需要注釋,注釋格式見實例。
實例:
*pq->OSQIn++=pmsg;/*Insertmessageintoqueue*/ pq->OSQEntries++;/*Updatethenbrofentriesinthequeue*/ if(pq->OSQIn==pq->OSQEnd) { pq->OSQIn=pq->OSQStart;/*WrapINptrifweareatendofqueue*/ }
說明:局部變量,關鍵語句需要注釋,從功能和意圖上進行注釋,而不是代碼的重復。多條注釋語句盡量保持對齊,實現美觀,整潔。
參考材料:
1. 《代碼整潔之道》(RobertC.Martin 著 韓磊 譯 人民郵電出版社2010年1月)第四章"注釋”。
2.《Doxygen中文手冊》
4 項目版本號命名規范
項目版本號管理是項目管理的重要方面,我們根據項目不同的開發階段制定了不同的版本號命名規范。
項目開發過程一般分為前期開發測試階段、發布階段、維護階段這三個主要階段,我們分別制定了命名規范。
4.1 開發、測試階段版本號命名
規則4.1-1(強制):處于開發、調試階段的項目,版本號使用“V0.yz”的形式。
說明:處于新開發、調試階段的項目,版本號使用“V0.yz” 的形式,比如新開發的項目正處在開發、調試階段,這時可以使用“ V0.10 ”這樣的版本號。
你認為完成了新的功能模塊或整體架構做了很大的修改,可以根據情況增加 Y 或者 Z的值。比如,你開發階段在“ V0.10 ”基礎上新增加了一個功能模塊你可以將版本號改為“V0.11”,做了比較大的修改,你可以將版本號定為“V0.20”。
4.2 正式發布階段版本號命名
規則4.2-1(強制):處于正式發布階段的項目,版本號使用“Vx.y”的形式。
說明:處于正式發布的項目版本號使用“Vx.y”的形式。比如,你發布了一個正式面向市場的項目,你可以使用“V1.0”作為正式的版本號。在“V1.0”基礎上增加功能的正式版本,你可以使用“V1.1”作為下一次正式版本的版本號,在“V1.0”基礎上修正了大的BUG或者做了很大的改動,你可以使用“V2.0”作為下一次正式版本號。
4.3 維護階段版本號命名
規則4.3-1(強制):處于維護階段的項目,版本號使用“Vx.yz”的形式。
說明:處于維護階段的項目版本號使用“Vx.yz”的形式。比如在"V1.1"的基礎上修改了一個功能實現算法以實現高效率,則可以使用"V1.11" 來表示這是在正式發布版本“V1.1”的基礎上進行的一次修正,再次修正可以使用“V1.12”。
5 嵌入式軟件安全性相關規范
5.1頭文件
原則5.1-1(強制):頭文件用于聲明模塊對外接口,包括具有外部鏈接的函數原型聲明、全局變量聲明、定義的類型聲明等。
說明:頭文件是模塊(Module)或單元(Unit)的對外接口。頭文件中應放置對外部的聲明,如對外提供的函數聲明、宏定義、類型定義等。內部使用的函數聲明不應放在頭文件中。內部使用的宏、枚舉、結構定義不應放入頭文件中。變量定義不應放在頭文件中,應放在.c文件中。
變量的聲明盡量不要放在頭文件中,亦即盡量不要使用全局變量作為接口。變量是模塊或單元的內部實現細節,不應通過在頭文件中聲明的方式直接暴露給外部,應通過函數接口的方式進行對外暴露。即使必須使用全局變量,也只應當在.c中定義全局變量,在.h中僅聲明變量為全局的。
參考材料:《C語言接口與實現》(David R. Hanson著 傅蓉 周鵬 張昆琪權威 譯 機械工業出版社 2004年1月)(英文版:"C Interfaces and Implementations")
規則5.1-2(強制):只能通過包含頭文件的方式使用其他.c提供的接口,禁止在.c中通過extern的方式使用外部函數接口、變量。
說明:若a.c使用了b.c定義的foo()函數 ,則應當在b.h中聲明externintfoo(int input);并在a.c中通過#include
規則5.1-3(強制):使用#define定義保護符,防止頭文件重復包含。
說明:多次包含一個頭文件可以通過認真的設計來避免。如果不能做到這一點,就需要采取阻止頭文件內容被包含多于一次的機制。通常的手段是為每個文件配置一個宏,當頭文件第一次被包含時就定義這個宏,并在頭文件被再次包含時使用它以排除文件內容。所有頭文件都應當使用#define 防止頭文件被多重包含,命名格式FILENAME_H_,其中FILENAME 為頭文件的名稱。
實例:
若文件名為:stm32f10x_adc.h。
#ifndefSTM32F10x_DAC_H_ #defineSTM32F10x_DAC_H_ ………… 受保護的代碼 #endif
5.2 預處理命令
規則5.2-1(強制):C的宏只能擴展為用大括號括起來的初始化、常量、小括號括起來的表達式、類型限定符、存儲類標識符或do-while-zero 結構。
說明:這些是宏當中所有可允許使用的形式。存儲類標識符和類型限定符包括諸如extern、static和const這樣的關鍵字。使用任何其他形式的#define 都可能導致非預期的行為,或者是非常難懂的代碼。
特別的,宏不能用于定義語句或部分語句,除了do-while 結構。宏也不能重定義語言的語法。
宏的替換列表中的所有括號,不管哪種形式的 ()、{} 、[] 都應該成對出現。do-while-zero 結構(見下面實例)是在宏語句體中唯一可接受的具有完整語句的形式。do-while-zero 結構用于封裝語句序列并確保其是正確的。
注意:在宏語句體的末尾必須省略分號。
實例:
以下是合理的宏定義:
#definePI3.14159F/*Constant*/ #defineXSTAL10000000/*Constant*/ #defineCLOCK(XSTAL/16)/*Constantexpression*/ #definePLUS2(X)((X)+2)/*Macroexpandingtoexpression*/ #defineSTORextern/*storageclassspecifier*/ #defineINIT(value){(value),0,0}/*bracedinitialiser*/ #defineREAD_TIME_32() do{ DISABLE_INTERRUPTS(); time_now=(INT32U)TIMER_HI<16;? ????????????????????time_now?=?time_now?|?(INT32U)?TIMER_LO;? ?????????????????????ENABLE_INTERRUPTS();? }?while(0)???????????????????????????/*?example?of?do-while-zero?*/
以下是不合理的宏定義:
#defineunsignedintlong/*usetypedefinstead*/ #defineSTARTIFif(/*unbalanced()andlanguageredefinition*/
規則5.2-2(強制):在定義函數宏時,每個參數實例都應該以小括號括起來。
實例:
一個abs 函數可以定義成:
#defineabs(x)(((x)>=0)?(x):-(x))
不能定義成:
#defineabs(x)(((x)>=0)?x:-x)
如果不堅持本規則,那么當預處理器替代宏進入代碼時,操作符優先順序將不會給出要求的結果。
考慮前面第二個不正確的定義被替代時會發生什么:
z=abs(a–b);
將給出如下結果:
z=((a–b>=0)?a–b:-a–b);
子表達式 – a - b 相當于 (-a)-b ,而不是希望的 –(a-b) 。
把所有參數都括進小括號中就可以避免這樣的問題。
規則5.2-3(建議):使用宏時,不允許參數數值發生變化。
實例:
如下用法可能導致錯誤。
#defineSQUARE(a)((a)*(a)) inta=5; intb; b = SQUARE(a++);/*結果:a = 7,即執行了兩次增。
正確的用法是:
b=SQUARE(a); a++;/*結果:a = 6,即只執行了一次增*/
同樣建議在調用函數時,參數也不要變化,如果某次軟件升級將其中一個接口由函數實現轉換成宏,那參數數值發生變化的調用將產生非預期效果。
規則5.2-4(建議):除非必要,應盡可能使用函數代替宏。
說明:宏能提供比函數優越的速度,但是沒有參數檢查機制,不當的使用可能產生非預期后果。
5.3 類型及類型轉換
規則5.3-1(強制):應該使用標明了大小和符號的typedef代替基本數據類型。不應使用基本數值類型char、int、short、long、float和double,而應使用typedef進行類型的定義。
說明:為了程序的跨平臺移植性,我們使用typedef定義指明了大小和符號的數據類型。
實例:
此實例是根據keil for ARM的數據類型大小進行的定義。
No. | 基本數據類型 | Typedef定義 |
1 | typedef unsigned char | BOOLEAN |
2 | typedef unsigned char | INT8U |
3 | typedef signed char | INT8S |
4 | typedef unsigned short | INT16U |
5 | typedef signed short | INT16S |
6 | typedef unsigned int | INT32U |
7 | typedef signed int | INT32S |
8 | typedef float | FP32 |
9 | typedef double | FP64 |
應根據硬件平臺和編譯器的信息對基本類型進行定義。
規則5.3-2(建議):浮點應用應該適應于已定義的浮點標準。
說明:浮點運算會帶來許多問題,一些問題(而不是全部)可以通過適應已定義的標準來克服。其中一個合適的標準是 ANSI/IEEE Std 754 [1] 。
5.3.1 顯式數據類型轉換
C 語言給程序員提供了相當大的自由度并允許不同數值類型可以自動轉換。由于某些功能性的原因可以引入顯式的強制轉換,例如:
1.用以改變類型使得后續的數值操作可以進行
2.用以截取數值
3.出于清晰的角度,用以執行顯式的類型轉換
為了代碼清晰的目的而插入的強制轉換通常是有用的,但如果過多使用就會導致程序的可讀性下降。正如下面所描述的,一些隱式轉換是可以安全地忽略的,而另一些則不能。
規則5.3.1-1(強制):強制轉換只能向表示范圍更窄的方向轉換,且與被轉換對象的類
型具有相同的符號。浮點類型值只能強制轉換到更窄的浮點類型。
說明:這條規則主要是要求需要強制轉換時,須明確被轉換對象的表示范圍及轉換后的表示范圍。轉換時盡量保持符號一致,不同符號對象之間不應出現強制轉換。向更寬數據范圍轉換并不能提高數據精確度,并沒有實際意義。在程序中盡量規劃好變量范圍,盡量少使用強制轉換。
規則5.3.1-2(強制):如果位運算符 ~ 和 < 應用在基本類型為unsigned char或unsignedshort 的操作數,結果應該立即強制轉換為操作數的基本類型。
說明:當這些操作符(~ 和<<)用在 small integer 類型(unsigned char 或unsigned short )時,運算之前要先進行整數提升,結果可能包含并非預期的高端數據位。
例如:
INT8Uport=0x5aU; NT8Uresult_8; INT16Uresult_16; INT16Umode; result_8=(~port)>>4;/*不合規范*/
~port的值在16位機器上是 0xffa5 ,而在 32 位機器上是 0xffffffa5 。在每種情況下,result的值是0xfa ,然而期望值可能是0x0a 。
這樣的危險可以通過如下所示的強制轉換來避免:
result_8=((INT8U)(~port))>>4;/*符合規范*/ result_16=((INT16U)(~(INT16U)port))>>4;/*符合規范*/
當<<操作符用在 smallinteger 類型時會遇到類似的問題,高端數據位被保留下來。
例如:
result_16=((port<4?)?&?mode?)?>>6;/*不符合規范*/ result_16 的值將依賴于 int 實現的大小。附加的強制轉換可以避免任何模糊性。 result_16=((INT16U)((INT16U)port<4?)?&?mode?)>>6;/*符合規范*/
5.3.2 隱式類型轉換
規則5.3.2-1(強制):以下類型之間不應該存在隱式類型轉換。
1)有符號和無符號之間沒有隱式轉換
2)整型和浮點類型之間沒有隱式轉換
3)沒有從寬類型向窄類型的隱式轉換
4)函數參數沒有隱式轉換
5)函數的返回表達式沒有隱式轉換
6)復雜表達式沒有隱式轉換
5.3.3 整數后綴
規則5.3.3-1(強制):后綴“U”應該用在所有unsigned 類型的常量上。
整型常量的類型是混淆的潛在來源,因為它依賴于許多因素的復雜組合,包括:
1)常數的量級
2)整數類型實現的大小
3)任何后綴的存在
4)數值表達的進制(即十進制、八進制或十六進制)
例如,整型常量“40000”在32位環境中是 int 類型,而在 16位環境中則是long 類型。值0x8000 在16位環境中是 unsigned int 類型,而在 32 位環境中則是(signed )int 類型。
注意:
1)任何帶有“U”后綴的值是unsigned 類型
2)一個不帶后綴的小于231的十進制值是signed 類型
但是:
1)不帶后綴的大于或等于215的十六進制數可能是 signed 或unsigned 類型
2)不帶后綴的大于或等于231的十進制數可能是 signed 或unsigned 類型
常量的符號應該明確。符號的一致性是構建良好形式的表達式的重要原則。如果一個常數是unsigned 類型,為其加上“U”后綴將有助于避免混淆。當用在較大數值上時,后綴也許是多余的(在某種意義上它不會影響常量的類型);然而后綴的存在對代碼的清晰性是種有價值的幫助。
5.3.4 指針類型轉換
指針類型可以歸為如下幾類:
1)對象指針
2)函數指針
3)void 指針
4)空(null )指針常量(即由數值 0 強制轉換為 void*類型)
涉及指針類型的轉換需要明確的強制,除非在以下時刻:
1)轉換發生在對象指針和void 指針之間,而且目標類型承載了源類型的所有類型標識符。
2)當空指針常量(void*)被賦值給任何類型的指針或與其做等值比較時,空指針常量被自動轉化為特定的指針類型。
C 當中只定義了一些特定的指針類型轉換,而一些轉換的行為是實現定義的。
規則5.3.9-1(強制):轉換不能發生在函數指針和其他除了整型之外的任何類型指針之間。
[Undefined]
說明:
函數指針到不同類型指針的轉換會導致未定義的行為。這意味著一個函數指
針不能轉換成指向不同類型函數的指針。
規則5.3.9-2(強制):對象指針和其他除整型之外的任何類型指針之間、對象指針和其他類型對象的指針之間、對象指針和void指針之間不能進行轉換。
[Undefined]
規則5.3.9-3(強制):不應在某類型對象指針和其他不同類型對象指針之間進行強制轉換。
說明:如果新的指針類型需要更嚴格的分配時這樣的轉換可能是無效的。
實例:
INT8U*p1; INT32U*p2; p2=(INT32U*)p1;/*不符規范*/
5.4 初始化、聲明與定義
規則5.4-1(強制):所有自動變量在使用前都應被賦值。
[Undefined]
說明:注意,根據ISO C[2] 標準,具有靜態存儲期的變量缺省地被自動賦予零值,除非經過了顯式的初始化。實際中,一些嵌入式環境沒有實現這樣的缺省行為。
靜態存儲期是所有以static存儲類形式聲明的變量或具有外部鏈接的變量的共同屬性,自動存儲期變量通常不是自動初始化的。
規則5.4-2(強制):應該使用大括號以指示和匹配數組和結構的非零初始化構造。
[Undefined]
說明:
ISO C[2]要求數組、結構和聯合的初始化列表要以一對大括號括起來(盡管不這樣做的行為是未定義的)。本規則更進一步地要求,使用附加的大括號來指示嵌套的結構。它迫使程序員顯式地考慮和描述復雜數據類型元素(比如,多維數組)的初始化次序。
例如,下面的例子是二維數組初始化的有效(在ISO C [2]中)形式,但第一個與本規則相違背:
在結構中以及在結構、數組和其他類型的嵌套組合中,規則類似。
還要注意的是,數組或結構的元素可以通過只初始化其首元素的方式初始化(為 0 或
NULL)。如果選擇了這樣的初始化方法,那么首元素應該被初始化為0(或NULL),此時不需要使用嵌套的大括號。
實例:
INT16Utest[3][2]={1,2,3,4,5,6};/*不符合此規則*/ INT16Utest[3][2]={{1,2},{3,4},{5,6}};/*符合此規則*/
規則5.4-3(強制):在枚舉列表中,“= ”不能顯式用于除首元素之外的元素上,除非所有的元素都是顯式初始化的。
說明:
如果枚舉列表的成員沒有顯式地初始化,那么C 將為其分配一個從0 開始的整數序列,首元素為0 ,后續元素依次加 1 。
如上規則允許的,首元素的顯式初始化迫使整數的分配從這個給定的值開始。當采用這種方法時,重要的是確保所用初始化值一定要足夠小,這樣列表中的后續值就不會超出該枚舉常量所用的int 存儲量。
列表中所有項目的顯式初始化也是允許的,它防止了易產生錯誤的自動與手動分配的混合。然而,程序員就該擔負職責以保證所有值都處在要求的范圍內以及值不是被無意復制的。
實例:
enum colour { red = 3, blue, green, yellow = 5 }; /* 不符合此規則 */
enum colour { red = 3, blue = 4, green = 5, yellow= 5 }; /* 符合此規則 */
雖然green和yellow的值都是5,但這符合規則。
enum colour { red = 1, blue, green, yellow }; /* 符合此規則 */
規則5.4-4(強制):函數應當具有原型聲明,且原型在函數的定義和調用范圍內都是可見的。
[Undefined]
說明:原型的使用使得編譯器能夠檢查函數定義和調用的完整性。如果沒有原型,就不會迫使編譯器檢查出函數調用當中的一定錯誤(比如,函數體具有不同的參數數目,調用和定義之間參數類型的不匹配)。
事實證明,函數接口是相當多問題的肇因,因此本規則是相當重要的。對外部函數來說,我們建議采用如下方法,在頭文件中聲明函數(亦即給出其原型),并在所有需要該函數原型的代碼文件中包含這個頭文件,在實現函數功能的.c文件中也包含具有原型聲明的頭文件。為具有內部鏈接的函數給出其原型也是良好的編程實踐。
規則5.4-5(強制):定義或聲明對象、函數時都應該顯示指明其類型。
規則5.4-6(強制):函數的每個參數類型在聲明和定義中必須是等同的,函數的返回類型也該是等同的。
[Undefined]
規則5.4-6(強制):函數應該聲明為具有文件作用域。
[Undefined]
說明:在塊作用域中聲明函數會引起混淆并可能導致未定義的行為。
規則5.4-7(強制):在文件范圍內聲明和定義的所有對象或函數應該具有內部鏈接,除非是在需要外部鏈接的情況下,具有內部鏈接屬性的對象或函數應該使用static關鍵字修飾。
說明:如果一個變量只是被同一文件中的函數所使用,那么就用static。類似地,如果一個函數只是在同一文件中的其他地方調用,那么就用 static。
使用 static存儲類標識符將確保標識符只是在聲明它的文件中是可見的,并且避免了和其他文件或庫中的相同標識符發生混淆的可能性。具有外部鏈接屬性的對象或函數在相應模塊的頭文件中聲明,在需要使用這些接口的模塊中包含此頭文件。
規則5.4-8(強制):當一個數組聲明為具有外部鏈接,它的大小應該顯式聲明或者通過初始化進行隱式定義。
[Undefined]
實例:
INT8Uarray[10];/*符合規范*/ externINT8Uarray[];/*不符合規范*/ INT8Uarray[]={0,10,15};/*符合規范*/
盡管可以在數組聲明不完善時訪問其元素,然而仍然是在數組的大小可以顯式確定的情況下,這樣做才會更為安全。
5.5 控制語句和表達式
規則5.5-1(建議):不要過分依賴C 表達式中的運算符優先規則。
說明:括號的使用除了可以覆蓋缺省的運算符優先級以外,還可以用來強調所使用的運算符。使用相當復雜的C 運算符優先級規則很容易引起錯誤,那么這種方法就可以幫助避免這樣的錯誤,并且可以使得代碼更為清晰可讀。
然而,過多的括號會分散代碼使其降低了可讀性。因此,請合理使用括號來提高程序清晰度和可讀性。
規則5.5-1(強制):不能在具有副作用的表達式中使用sizeof 運算符。
說明:當一個表達式使用了sizeof運算符,并期望計算表達式的值時,表達式是不會被計算的。sizeof只對表達式的類型有用。
實例:
INT32S i;
INT32S j;
j = sizeof (i = 1234);
/*j的值是i類型的大小,但i的值并沒有賦值成1234 */
規則5.5-2(強制):邏輯運算符 && 或 || 的右手操作數不能包含副作用。
說明:C語言中存在表達式的某些部分不會被計算到,這取決于表達式中的其他部分。邏輯操作符&&或||在進行邏輯判斷時,若僅判別左操作數就能確定true or false的情況下,邏輯操作符的右操數將被忽略。
實例:
if ( high && ( x == i++ ) ) /* 不符合規則 */
若high為false,則整個表達式的布爾值也即為false,不用再去執行和判斷右操作數。
規則5.5-3(建議):邏輯運算符(&&、| | 和 ! )的操作數應該是有效的布爾數。有效布爾類型的表達式不能用做非邏輯運算符(&&、| | 和 ! )的操作數。
說明:有效布爾類型是表示真、假的一種數據類型,產生布爾類型的可以是比較,邏輯運算,但布爾類型數據只能進行邏輯運算。
規則5.5-4(強制):位運算符不能用于基本類型(underlying type )是有符號的操作數上。
[Implementation-defined]
說明:位運算(~ 、<<、>>、&、^ 和 | )對有符號整數通常是無意義的。比如,如果右移運算把符號位移動到數據位上或者左移運算把數據位移動到符號位上,就會產生問題。
規則5.5-6(建議):在一個表達式中,自增(++)和自減(- - )運算符不應同其他運算符混合在一起。
說明:不建議使用同其他算術運算符混合在一起的自增和自減運算符是因為
1)它顯著削弱了代碼的可讀性;
2)在不同的變異環境下,會執行不同的運算次序,產生不同結果。
實例:
u8a = ++u8b +u8c--; /* 不符合規范 */
下面的序列更為清晰和安全:
++u8b; u8a=u8b+u8c; u8c--;
規則5.5-7(強制):浮點表達式不能做像‘>’ ‘<’ ‘==’ ‘!=’等 關系運算。
說明:float、double類型的數據都有一定的精確度限制,使用不同浮點數表示規范或者不同硬件平臺可能導致關系運算的結果不一致。
規則5.5-8(強制):for語句的三個表達式應該只關注循環控制,for循環中用于計數的變量不應在循環體中修改。
說明:for 語句的三個表達式都給出時它們應該只用于如下目的:
第一個表達式初始化循環計數器;
第二個表達式包含對循環計數器和其他可選的循環控制變量的測試;
第三個表達式循環計數器的遞增或遞減。
規則5.5-9(強制):組成switch、while、do...while 或for 結構體的語句應該是復合語句。即使該復合語句只包含一條語句也要擴在{}里。
實例:
for(i=0;i
規則5.5-10(強制):if /else應該成對出現。所有的if ... else if 結構應該由else 子句結束。
規則5.5-11(強制):switch 語句中如果case 分支的內容不為空,那么必須以break 作為結束,最后分支應該是default分支。
5.6 函數
原則5.6-1(強制):編寫整潔函數,同時把代碼有效組織起來。
說明:代碼簡單直接、不隱藏設計者的意圖、用干凈利落的抽象和直截了當的控制語句將函數有機組織起來。代碼的有效組織包括:邏輯層組織和物理層組織兩個方面。邏輯層,主要是把不同功能的函數通過某種聯系組織起來,主要關注模塊間的接口,也就是模塊的架構。
物理層,無論使用什么樣的目錄或者名字空間等,需要把函數用一種標準的方法組織起來。例如:設計良好的目錄結構、函數名字、文件組織等,這樣可以方便查找。
規則5.6-2(強制):一定要顯示聲明函數的返回值類型,及所帶的參數。如果沒有要聲明為void。
說明:C語言中不加類型說明的函數,一律自動按整型處理。
規則5.6-3(建議):不建議使用遞歸函數調用。
說明:有些算法使用分而治之的遞歸思想,但在嵌入式中棧空間有限,遞歸本身承載著可用堆棧空間過度的危險,這能導致嚴重的錯誤。除非遞歸經過了非常嚴格的控制,否則不可能在執行之前確定什么是最壞情況(worst-case)的堆棧使用。
5.7 指針與數組
規則5.7-1(強制):除了指向同一數組的指針外,不能用指針進行數學運算,不能進行關系運算。
說明:這樣做的目的一是使代碼清晰易讀,另外避免訪問無效的內存地址。
規則5.7-2(強制):指針在使用前一定要賦值,避免產生野指針。
規則5.7-3(強制):不要返回局部變量的地址。
說明:
局部變量是在棧中分配的,函數返回后占用的內存會釋放,繼續使用這樣的內存是危險的。因此,應該避免出現這樣的危險。
實例:
INT8U*foobar(void) { INT8Ulocal_auto; return(&local_auto);/*不符合規范*/ }
5.8 結構與聯合
原則5.8-1(強制):結構功能單一,不要設計面面俱到的數據結構。
說明:相關的一組信息才是構成一個結構體的基礎,結構的定義應該可以明確的描述一個對象,而不是一組相關性不強的數據的集合。設計結構時應力爭使結構代表一種現實事務的抽象,而不是同時代表多種。
結構中的各元素應代表同一事務的不同側面,而不應把描述沒有關系或關系很弱的不同事務的元素放到同一結構中。
5.9 標準庫
規則5.9-1(強制):標準庫中保留的標識符、宏和函數不能被定義、重定義或取消定義。
[Undefined]
說明:通常 #undef 一個定義在標準庫中的宏是件壞事。同樣不好的是,#define 一個宏名字,而該名字是C 的保留標識符或者標準庫中做為宏、對象或函數名字的C 關鍵字。
例如,存在一些特殊的保留字和函數名字,它們的作用為人所熟知,如果對它們重新定義或取消定義就會產生一些未定義的行為。這些名字包括defined、__LINE__、__FILE__、__DATE__ 、__TIME__、__STDC__、errno和assert。
規則5.9-2(強制):傳遞給庫函數的值必須檢查其有效性。
說明:
C 標準庫中的許多函數根據ISO [2] 標準 并不需要檢查傳遞給它們的參數的有效性。即使標準要求這樣,或者編譯器的編寫者聲明要這么做,也不能保證會做出充分的檢查。因此,程序員應該為所有帶有嚴格輸入域的庫函數(標準庫、第三方庫及自己定義的庫)提供適當的輸入值檢查機制。
具有嚴格輸入域并需要檢查的函數例子為:
math.h 中的許多數學函數,比如:
負數不能傳遞給sqrt 或log函數;
fmod 函數的第二個參數不能為零
toupper 和tolower:當傳遞給toupper函數的參數不是小寫字符時,某些實現能產生并非預期的結果(tolower 函數情況類似)
如果為ctype.h 中的字符測試函數傳遞無效的值時會給出未定義的行為
應用于大多數負整數的abs 函數給出未定義的行為 在math.h 中,盡管大多數數學庫函數定義了它們允許的輸入域,但在域發生錯誤時它們的返回值仍可能隨編譯器的不同而不同。因此,對這些函數來說,預先檢查其輸入值的有效性就變得至關重要。
程序員在使用函數時,應該識別應用于這些函數之上的任何的域限制(這些限制可能
會也可能不會在文檔中說明),并且要提供適當的檢查以確認這些輸入值位于各自域
中。當然,在需要時,這些值還可以更進一步加以限制。
有許多方法可以滿足本規則的要求,包括:
1.調用函數前檢查輸入值
2. 設計深入函數內部的檢查手段。這種方法尤其適應于實驗室內開發的庫,縱然它也可以用于買進的第三方庫(如果第三方庫的供應商聲明他們已內置了檢查的話)。
3. 產生函數的“封裝”(wrapped)版本,在該版本中首先檢查輸入,然后調用原始的函數。
4. 靜態地聲明輸入參數永遠不會采取無效的值。
注意,在檢查函數的浮點參數時(浮點參數在零點上為奇點),適當的做法是執行其是否為零的檢查。然而如果當參數趨近于零時,函數值的量級趨近無窮的話,仍然有必要檢查其在零點(或其他任何奇點)上的容限,這樣可以避免溢出的發生。
-
嵌入式
+關注
關注
5068文章
19017瀏覽量
303258 -
C語言
+關注
關注
180文章
7598瀏覽量
136188 -
編譯器
+關注
關注
1文章
1618瀏覽量
49050
原文標題:嵌入式C語言的自我修養:這樣編出來的代碼簡直行云流水!
文章出處:【微信號:wujianying_danpianji,微信公眾號:單片機精講吳鑒鷹】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論