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