本文是大廠C代碼規(guī)范,有點(diǎn)長(zhǎng),有時(shí)間可以學(xué)習(xí)下。
1 代碼總體原則
1、清晰第一
清晰性是易于維護(hù)、易于重構(gòu)的程序必需具備的特征。代碼首先是給人讀的,好的代碼應(yīng)當(dāng)可以像文章一樣發(fā)聲朗誦出來。
目前軟件維護(hù)期成本占整個(gè)生命周期成本的40%~90%。根據(jù)業(yè)界經(jīng)驗(yàn),維護(hù)期變更代碼的成本,小型系統(tǒng)是開發(fā)期的5倍,大型系統(tǒng)(100萬行代碼以上)可以達(dá)到100倍。業(yè)界的調(diào)查指出,開發(fā)組平均大約一半的人力用于彌補(bǔ)過去的錯(cuò)誤,而不是添加新的功能來幫助公司提高競(jìng)爭(zhēng)力。
一般情況下,代碼的可閱讀性高于性能,只有確定性能是瓶頸時(shí),才應(yīng)該主動(dòng)優(yōu)化。
2、簡(jiǎn)潔為美
簡(jiǎn)潔就是易于理解并且易于實(shí)現(xiàn)。代碼越長(zhǎng)越難以看懂,也就越容易在修改時(shí)引入錯(cuò)誤。寫的代碼越多,意味著出錯(cuò)的地方越多,也就意味著代碼的可靠性越低。因此,我們提倡大家通過編寫簡(jiǎn)潔明了的代碼來提升代碼可靠性。
廢棄的代碼(沒有被調(diào)用的函數(shù)和全局變量)要及時(shí)清除,重復(fù)代碼應(yīng)該盡可能提煉成函數(shù)。
3、選擇合適的風(fēng)格,與代碼原有風(fēng)格保持一致
產(chǎn)品所有人共同分享同一種風(fēng)格所帶來的好處,遠(yuǎn)遠(yuǎn)超出為了統(tǒng)一而付出的代價(jià)。在公司已有編碼規(guī)范的指導(dǎo)下,審慎地編排代碼以使代碼盡可能清晰,是一項(xiàng)非常重要的技能。如果重構(gòu)/ / 修改其他風(fēng)格的代碼時(shí),比較明智的做法是根據(jù) 現(xiàn)有 代碼 的 現(xiàn)有風(fēng)格繼續(xù)編寫代碼,或者使用格式轉(zhuǎn)換工具進(jìn)行轉(zhuǎn)換成公司內(nèi)部風(fēng)格。
2、頭文件
對(duì)于C語言來說,頭文件的設(shè)計(jì)體現(xiàn)了大部分的系統(tǒng)設(shè)計(jì)。不合理的頭文件布局是編譯時(shí)間過長(zhǎng)的根因,不合理的頭文件實(shí)際上反映了不合理的設(shè)計(jì)。
1、頭文件中適合放置接口的聲明,不適合放置實(shí)現(xiàn)
頭文件是模塊(Module)或單元(Unit)的對(duì)外接口。頭文件中應(yīng)放置對(duì)外部的聲明,如對(duì)外提供的函數(shù)聲明、宏定義、類型定義等。
要求:
內(nèi)部使用的函數(shù)(相當(dāng)于類的私有方法)聲明不應(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中僅聲明變量為全局的。
2、頭文件應(yīng)當(dāng)職責(zé)單一,切忌依賴復(fù)雜
頭文件過于復(fù)雜,依賴過于復(fù)雜是導(dǎo)致編譯時(shí)間過長(zhǎng)的主要原因。很多現(xiàn)有代碼中頭文件過大,職責(zé)過多,再加上循環(huán)依賴的問題,可能導(dǎo)致為了在.c中使用一個(gè)宏,而包含十幾個(gè)頭文件。
錯(cuò)誤示例:某平臺(tái)定義WORD類型的頭文件:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
…
typedef unsigned short WORD;
…
這個(gè)頭文件不但定義了基本數(shù)據(jù)類型WORD,還包含了stdio.h syslib.h等等不常用的頭文件。如果工程中有10000個(gè)源文件,而其中100個(gè)源文件使用了stdio.h的printf,由于上述頭文件的職責(zé)過于龐大,而WORD又是每一個(gè)文件必須包含的,從而導(dǎo)致stdio.h/syslib.h等可能被不必要的展開了9900次,大大增加了工程的編譯時(shí)間。
3、頭文件應(yīng)向穩(wěn)定的方向包含
頭文件的包含關(guān)系是一種依賴,一般來說,應(yīng)當(dāng)讓不穩(wěn)定的模塊依賴穩(wěn)定的模塊,從而當(dāng)不穩(wěn)定的模塊發(fā)生變化時(shí),不會(huì)影響(編譯)穩(wěn)定的模塊。
就我們的產(chǎn)品來說,依賴的方向應(yīng)該是:產(chǎn)品依賴于平臺(tái),平臺(tái)依賴于標(biāo)準(zhǔn)庫。某產(chǎn)品線平臺(tái)的代碼中已經(jīng)包含了產(chǎn)品的頭文件,導(dǎo)致平臺(tái)無法單獨(dú)編譯、發(fā)布和測(cè)試,是一個(gè)非常糟糕的反例。除了不穩(wěn)定的模塊依賴于穩(wěn)定的模塊外,更好的方式是兩個(gè)模塊共同依賴于接口,這樣任何一個(gè)模塊的內(nèi)部實(shí)現(xiàn)更改都不需要重新編譯另外一個(gè)模塊。在這里,我們假設(shè)接口本身是最穩(wěn)定的。
4、每一個(gè) .c 文件應(yīng)有一個(gè)同名 .h 文件,用于聲明需要對(duì)外公開的接口
如果一個(gè).c文件不需要對(duì)外公布任何接口,則其就不應(yīng)當(dāng)存在,除非它是程序的入口,如main函數(shù)所在的文件。
現(xiàn)有某些產(chǎn)品中,習(xí)慣一個(gè).c文件對(duì)應(yīng)兩個(gè)頭文件,一個(gè)用于存放對(duì)外公開的接口,一個(gè)用于存放內(nèi)部需要用到的定義、聲明等,以控制.c文件的代碼行數(shù)。編者不提倡這種風(fēng)格。這種風(fēng)格的根源在于源文件過大,應(yīng)首先考慮拆分.c文件,使之不至于太大。另外,一旦把私有定義、聲明放到獨(dú)立的頭文件中,就無法從技術(shù)上避免別人include之,難以保證這些定義最后真的只是私有的。
5、禁止頭文件循環(huán)依賴
頭文件循環(huán)依賴,指a.h包含b.h,b.h包含c.h,c.h包含a.h之類導(dǎo)致任何一個(gè)頭文件修改,都導(dǎo)致所有包含了a.h/b.h/c.h的代碼全部重新編譯一遍。而如果是單向依賴,如a.h包含b.h,b.h包含c.h,而c.h不包含任何頭文件,則修改a.h不會(huì)導(dǎo)致包含了b.h/c.h的源代碼重新編譯。
6、 .c/.h文件禁止包含用不到的頭文件
很多系統(tǒng)中頭文件包含關(guān)系復(fù)雜,開發(fā)人員為了省事起見,可能不會(huì)去一一鉆研,直接包含一切想到的頭文件,甚至有些產(chǎn)品干脆發(fā)布了一個(gè)god.h,其中包含了所有頭文件,然后發(fā)布給各個(gè)項(xiàng)目組使用,這種只圖一時(shí)省事的做法,導(dǎo)致整個(gè)系統(tǒng)的編譯時(shí)間進(jìn)一步惡化,并對(duì)后來人的維護(hù)造成了巨大的麻煩。
7、 頭文件應(yīng)當(dāng)自包含
簡(jiǎn)單的說,自包含就是任意一個(gè)頭文件均可獨(dú)立編譯。如果一個(gè)文件包含某個(gè)頭文件,還要包含另外一個(gè)頭文件才能工作的話,就會(huì)增加交流障礙,給這個(gè)頭文件的用戶增添不必要的負(fù)擔(dān)。
示例:如果a.h不是自包含的,需要包含b.h才能編譯,會(huì)帶來的危害:每個(gè)使用a.h頭文件的.c文件,為了讓引入的a.h的內(nèi)容編譯通過,都要包含額外的頭文件b.h。額外的頭文件b.h必須在a.h之前進(jìn)行包含,這在包含順序上產(chǎn)生了依賴。
注意:該規(guī)則需要與“.c/.h文件禁止包含用不到的頭文件”規(guī)則一起使用,不能為了讓a.h自包含,而在a.h中包含不必要的頭文件。a.h要?jiǎng)倓偪梢宰园荒茉赼.h中多包含任何滿足自包含之外的其他頭文件。
8、總是編寫內(nèi)部 #include 保護(hù)符( #define 保護(hù))
多次包含一個(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,為了保證唯一性,更好的命名是PROJECTNAME_PATH_FILENAME_H。
注:沒有在宏最前面加上單下劃線"_",是因?yàn)橐话阋詥蜗聞澗€"_"和雙下劃線"__"開頭的標(biāo)識(shí)符為ANSIC等使用,在有些靜態(tài)檢查工具中,若全局可見的標(biāo)識(shí)符以"_"開頭會(huì)給出告警。
定義包含保護(hù)符時(shí),應(yīng)該遵守如下規(guī)則:
保護(hù)符使用唯一名稱;
不要在受保護(hù)部分的前后放置代碼或者注釋。
正確示例:假定VOS工程的timer模塊的timer.h,其目錄為VOS/include/timer/timer.h,應(yīng)按如下方式保護(hù):
#ifndef VOS_INCLUDE_TIMER_TIMER_H
#define VOS_INCLUDE_TIMER_TIMER_H
...
#endif
也可以使用如下簡(jiǎn)單方式保護(hù):
#ifndef TIMER_H
#define TIMER_H
...
#endif
例外情況:頭文件的版權(quán)聲明部分以及頭文件的整體注釋部分(如闡述此頭文件的開發(fā)背景、使用注意事項(xiàng)等)可以放在保護(hù)符(#ifndef XX_H)前面。
9、禁止在頭文件中定義變量
在頭文件中定義變量,將會(huì)由于頭文件被其他.c文件包含而導(dǎo)致變量重復(fù)定義。
10、只能通過包含頭文件的方式使用其他 .c 提供的接口,禁止在.c 中通過 extern 的方式使用外部函數(shù)接口、變量
若a.c使用了b.c定義的foo()函數(shù),則應(yīng)當(dāng)在b.h中聲明extern int foo(int input);并在a.c中通過#include
11、禁止在 extern "C" 中包含頭文件
在extern "C"中包含頭文件,會(huì)導(dǎo)致extern "C"嵌套,Visual Studio對(duì)extern "C"嵌套層次有限制,嵌套層次太多會(huì)編譯錯(cuò)誤。
在extern "C"中包含頭文件,可能會(huì)導(dǎo)致被包含頭文件的原有意圖遭到破壞。
錯(cuò)誤示例:
extern “C”
{
#include “xxx.h”
...
}
正確示例:
#include “xxx.h”
extern “C”
{
...
}
12、一個(gè)模塊通常包含多個(gè) .c 文件,建議放在同一個(gè)目錄下,目錄名即為模塊名。為方便外部使用者,建議每一個(gè)模塊提供一個(gè) .h ,文件名為目錄名
需要注意的是,這個(gè).h并不是簡(jiǎn)單的包含所有內(nèi)部的.h,它是為了模塊使用者的方便,對(duì)外整體提供的模塊接口。以Google test(簡(jiǎn)稱GTest)為例,GTest作為一個(gè)整體對(duì)外提供C++單元測(cè)試框架,其1.5版本的gtest工程下有6個(gè)源文件和12個(gè)頭文件。但是它對(duì)外只提供一個(gè)gtest.h,只要包含gtest.h即可使用GTest提供的所有對(duì)外提供的功能,使用者不必關(guān)系GTest內(nèi)部各個(gè)文件的關(guān)系,即使以后GTest的內(nèi)部實(shí)現(xiàn)改變了,比如把一個(gè)源文件c拆成兩個(gè)源文件,使用者也不必關(guān)心,甚至如果對(duì)外功能不變,連重新編譯都不需要。對(duì)于有些模塊,其內(nèi)部功能相對(duì)松散,可能并不一定需要提供這個(gè).h,而是直接提供各個(gè)子模塊或者.c的頭文件。
比如產(chǎn)品普遍使用的VOS,作為一個(gè)大模塊,其內(nèi)部有很多子模塊,他們之間的關(guān)系相對(duì)比較松散,就不適合提供一個(gè)vos.h。而VOS的子模塊,如Memory(僅作舉例說明,與實(shí)際情況可能有所出入),其內(nèi)部實(shí)現(xiàn)高度內(nèi)聚,雖然其內(nèi)部實(shí)現(xiàn)可能有多個(gè).c和.h,但是對(duì)外只需要提供一個(gè)Memory.h聲明接口。
13、如果一個(gè)模塊包含多個(gè)子模塊,則建議每一個(gè)子模塊提供一個(gè)對(duì)外的 .h,文件名為子模塊名
降低接口使用者的編寫難度
14、頭文件不要使用非習(xí)慣用法的擴(kuò)展名,如 .inc
目前很多產(chǎn)品中使用了.inc作為頭文件擴(kuò)展名,這不符合c語言的習(xí)慣用法。在使用.inc作為頭文件擴(kuò)展名的產(chǎn)品,習(xí)慣上用于標(biāo)識(shí)此頭文件為私有頭文件。但是從產(chǎn)品的實(shí)際代碼來看,這一條并沒有被遵守,一個(gè).inc文件被多個(gè).c包含比比皆是。
除此之外,使用.inc還導(dǎo)致source insight、Visual stduio等IDE工具無法識(shí)別其為頭文件,導(dǎo)致很多功能不可用,如“跳轉(zhuǎn)到變量定義處”。雖然可以通過配置,強(qiáng)迫IDE識(shí)別.inc為頭文件,但是有些軟件無法配置,如Visual Assist只能識(shí)別.h而無法通過配置識(shí)別.inc。
15、同一產(chǎn)品統(tǒng)一包含頭文件排列方式
常見的包含頭文件排列方式:功能塊排序、文件名升序、穩(wěn)定度排序。
正確示例1:以升序方式排列頭文件可以避免頭文件被重復(fù)包含:
#include
#include
#include
#include
#include
正確示例2:以穩(wěn)定度排序,建議將不穩(wěn)定的頭文件放在前面,如把產(chǎn)品的頭文件放在平臺(tái)的頭文件前面:
#include
#include
相對(duì)來說,product.h修改的較為頻繁,如果有錯(cuò)誤,不必編譯platform.h就可以發(fā)現(xiàn)product.h的錯(cuò)誤,可以部分減少編譯時(shí)間。
3 函數(shù)
函數(shù)設(shè)計(jì)的精髓:編寫整潔函數(shù),同時(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ù)名字、文件組織等,這樣可以方便查找。
1、一個(gè)函數(shù)僅完成一件功能
一個(gè)函數(shù)實(shí)現(xiàn)多個(gè)功能給開發(fā)、使用、維護(hù)都帶來很大的困難。
將沒有關(guān)聯(lián)或者關(guān)聯(lián)很弱的語句放到同一函數(shù)中,會(huì)導(dǎo)致函數(shù)職責(zé)不明確,難以理解,難以測(cè)試和改動(dòng)。
2、重復(fù)代碼應(yīng)該盡可能提煉成函數(shù)
重復(fù)代碼提煉成函數(shù)可以帶來維護(hù)成本的降低。
重復(fù)代碼是我司不良代碼最典型的特征之一。在“代碼能用就不改”的指導(dǎo)原則之下,大量的煙囪式設(shè)計(jì)及其實(shí)現(xiàn)充斥著各產(chǎn)品代碼之中。新需求增加帶來的代碼拷貝和修改,隨著時(shí)間的遷移,產(chǎn)品中堆砌著許多類似或者重復(fù)的代碼。
項(xiàng)目組應(yīng)當(dāng)使用代碼重復(fù)度檢查工具,在持續(xù)集成環(huán)境中持續(xù)檢查代碼重復(fù)度指標(biāo)變化趨勢(shì),并對(duì)新增重復(fù)代碼及時(shí)重構(gòu)。當(dāng)一段代碼重復(fù)兩次時(shí),即應(yīng)考慮消除重復(fù),當(dāng)代碼重復(fù)超過三次時(shí),應(yīng)當(dāng)立刻著手消除重復(fù)。
3、避免函數(shù)過長(zhǎng),新增函數(shù)不超過 50 行 (非空非注釋行)
過長(zhǎng)的函數(shù)往往意味著函數(shù)功能不單一,過于復(fù)雜。
函數(shù)的有效代碼行數(shù),即NBNC(非空非注釋行)應(yīng)當(dāng)在[1,50]區(qū)間。
例外:某些實(shí)現(xiàn)算法的函數(shù),由于算法的聚合性與功能的全面性,可能會(huì)超過50行。
延伸閱讀材料:業(yè)界普遍認(rèn)為一個(gè)函數(shù)的代碼行不要超過一個(gè)屏幕,避免來回翻頁影響閱讀;一般的代碼度量工具建議都對(duì)此進(jìn)行檢查,例如Logiscope的函數(shù)度量:"Number of Statement" (函數(shù)中的可執(zhí)行語句數(shù))建議不超過20行,QA C建議一個(gè)函數(shù)中的所有行數(shù)(包括注釋和空白行)不超過50行。
4、避免函數(shù)的代碼塊嵌套過深,新增函數(shù)的代碼塊嵌套不超過4層
函數(shù)的代碼塊嵌套深度指的是函數(shù)中的代碼控制塊(例如:if、for、while、switch等)之間互相包含的深度。每級(jí)嵌套都會(huì)增加閱讀代碼時(shí)的腦力消耗,因?yàn)樾枰谀X子里維護(hù)一個(gè)“棧”(比如,進(jìn)入條件語句、進(jìn)入循環(huán)??)。應(yīng)該做進(jìn)一步的功能分解,從而避免使代碼的閱讀者一次記住太多的上下文。優(yōu)秀代碼參考值:[1, 4]。
錯(cuò)誤示例:代碼嵌套深度為5層:
void serial (void)
{
if (!Received)
{
TmoCount = 0;
switch (Buff)
{
case AISGFLG:
if ((TiBuff.Count > 3)&& ((TiBuff.Buff[0] == 0xff) || (TiBuf.Buff[0] == CurPa.ADDR)))
{
Flg7E = false;
Received = true;
}
else
{
TiBuff.Count = 0;
Flg7D = false;
Flg7E = true;
}
break;
default:
break;
}
}
}
5、 可重入函數(shù)應(yīng)避免使用共享變量;若需要使用,則應(yīng)通過互斥手段(關(guān)中斷、信號(hào)量)對(duì)其加以保護(hù)
可重入函數(shù)是指可能被多個(gè)任務(wù)并發(fā)調(diào)用的函數(shù)。在多任務(wù)操作系統(tǒng)中,函數(shù)具有可重入性是多個(gè)任務(wù)可以共用此函數(shù)的必要條件。共享變量指的全局變量和static變量。編寫C語言的可重入函數(shù)時(shí),不應(yīng)使用static局部變量,否則必須經(jīng)過特殊處理,才能使函數(shù)具有可重入性。
示例:函數(shù)square_exam返回g_exam平方值。那么如下函數(shù)不具有可重入性。
int g_exam;
unsigned int example( int para )
{
unsigned int temp;
g_exam = para; // (**)
temp = square_exam ( );
return temp;
}
此函數(shù)若被多個(gè)線程調(diào)用的話,其結(jié)果可能是未知的,因?yàn)楫?dāng)(**)語句剛執(zhí)行完后,另外一個(gè)使用本函數(shù)的線程可能正好被激活,那么當(dāng)新激活的線程執(zhí)行到此函數(shù)時(shí),將使g_exam賦于另一個(gè)不同的para值,所以當(dāng)控制重新回到“temp =square_exam ( )”后,計(jì)算出的temp很可能不是預(yù)想中的結(jié)果。此函數(shù)應(yīng)如下改進(jìn)。
int g_exam;
unsigned int example( int para )
{
unsigned int temp;
[申請(qǐng)信號(hào)量操作] // 若申請(qǐng)不到“信號(hào)量”,說明另外的進(jìn)程正處于
g_exam = para; //給g_exam賦值并計(jì)算其平方過程中(即正在使用此
temp = square_exam( ); // 信號(hào)),本進(jìn)程必須等待其釋放信號(hào)后,才可繼
[釋放信號(hào)量操作] // 續(xù)執(zhí)行。其它線程必須等待本線程釋放信號(hào)量后
// 才能再使用本信號(hào)。
return temp;
}
6、對(duì)參數(shù)的合法性檢查,由調(diào)用者負(fù)責(zé)還是由接口函數(shù)負(fù)責(zé),應(yīng)在項(xiàng)目組/模塊內(nèi)應(yīng)統(tǒng)一規(guī)定。缺省由調(diào)用者負(fù)責(zé)。
對(duì)于模塊間接口函數(shù)的參數(shù)的合法性檢查這一問題,往往有兩個(gè)極端現(xiàn)象,即:要么是調(diào)用者和被調(diào)用者對(duì)參數(shù)均不作合法性檢查,結(jié)果就遺漏了合法性檢查這一必要的處理過程,造成問題隱患;要么就是調(diào)用者和被調(diào)用者均對(duì)參數(shù)進(jìn)行合法性檢查,這種情況雖不會(huì)造成問題,但產(chǎn)生了冗余代碼,降低了效率。
7、對(duì)函數(shù)的錯(cuò)誤返回碼要全面處理
一個(gè)函數(shù)(標(biāo)準(zhǔn)庫中的函數(shù)/第三方庫函數(shù)/用戶定義的函數(shù))能夠提供一些指示錯(cuò)誤發(fā)生的方法。這可以通過使用錯(cuò)誤標(biāo)記、特殊的返回?cái)?shù)據(jù)或者其他手段,不管什么時(shí)候函數(shù)提供了這樣的機(jī)制,調(diào)用程序應(yīng)該在函數(shù)返回時(shí)立刻檢查錯(cuò)誤指示。
8、設(shè)計(jì)高扇入,合理扇出(小于7)的函數(shù)
扇出是指一個(gè)函數(shù)直接調(diào)用(控制)其它函數(shù)的數(shù)目,而扇入是指有多少上級(jí)函數(shù)調(diào)用它。
扇出過大,表明函數(shù)過分復(fù)雜,需要控制和協(xié)調(diào)過多的下級(jí)函數(shù);而扇出過小,例如:總是1,表明函數(shù)的調(diào)用層次可能過多,這樣不利于程序閱讀和函數(shù)結(jié)構(gòu)的分析,并且程序運(yùn)行時(shí)會(huì)對(duì)系統(tǒng)資源如堆棧空間等造成壓力。通常函數(shù)比較合理的扇出(調(diào)度函數(shù)除外)通常是3~5。
扇出太大,一般是由于缺乏中間層次,可適當(dāng)增加中間層次的函數(shù)。扇出太小,可把下級(jí)函數(shù)進(jìn)一步分解多個(gè)函數(shù),或合并到上級(jí)函數(shù)中。當(dāng)然分解或合并函數(shù)時(shí),不能改變要實(shí)現(xiàn)的功能,也不能違背函數(shù)間的獨(dú)立性。扇入越大,表明使用此函數(shù)的上級(jí)函數(shù)越多,這樣的函數(shù)使用效率高,但不能違背函數(shù)間的獨(dú)立性而單純地追求高扇入。公共模塊中的函數(shù)及底層函數(shù)應(yīng)該有較高的扇入。
較良好的軟件結(jié)構(gòu)通常是頂層函數(shù)的扇出較高,中層函數(shù)的扇出較少,而底層函數(shù)則扇入到公共模塊中。
9、廢棄代碼(沒有被調(diào)用的函數(shù)和變量) ) 要及時(shí)清除
程序中的廢棄代碼不僅占用額外的空間,而且還常常影響程序的功能與性能,很可能給程序的測(cè)試、維護(hù)等造成不必要的麻煩。
10、函數(shù)不變參數(shù)使用const
不變的值更易于理解/跟蹤和分析,把const作為默認(rèn)選項(xiàng),在編譯時(shí)會(huì)對(duì)其進(jìn)行檢查,使代碼更牢固/更安全。
正確示例:C99標(biāo)準(zhǔn) 7.21.4.4 中strncmp 的例子,不變參數(shù)聲明為const。
int strncmp(const char *s1, const char *s2, register size_t n)
{
register unsigned char u1, u2;
while (n-- > 0)
{
u1 = (unsigned char) *s1++;
u2 = (unsigned char) *s2++;
if (u1 != u2)
{
return u1 - u2;
}
if (u1 == '')
{
return 0;
}
}
return 0;
}
11、函數(shù)應(yīng)避免使用全局變量、靜態(tài)局部變量和 I/O 操作,不可避免的地方應(yīng)集中使用
帶有內(nèi)部“存儲(chǔ)器”的函數(shù)的功能可能是不可預(yù)測(cè)的,因?yàn)樗妮敵隹赡苋Q于內(nèi)部存儲(chǔ)器(如某標(biāo)記)的狀態(tài)。這樣的函數(shù)既不易于理解又不利于測(cè)試和維護(hù)。在C語言中,函數(shù)的static局部變量是函數(shù)的內(nèi)部存儲(chǔ)器,有可能使函數(shù)的功能不可預(yù)測(cè)。
錯(cuò)誤示例:如下函數(shù),其返回值(即功能)是不可預(yù)測(cè)的。
unsigned int integer_sum( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0;// 注意,是static類型的。
// 若改為auto類型,則函數(shù)即變?yōu)榭深A(yù)測(cè)。
for (index = 1; index <= base; index++)
{
sum += index;
}
return sum;
}
12、檢查函數(shù)所有非參數(shù)輸入的有效性,如數(shù)據(jù)文件、公共變量等
函數(shù)的輸入主要有兩種:一種是參數(shù)輸入;另一種是全局變量、數(shù)據(jù)文件的輸入,即非參數(shù)輸入。函數(shù)在使用輸入?yún)?shù)之前,應(yīng)進(jìn)行有效性檢查。
13、 函數(shù)的參數(shù)個(gè)數(shù)不超過5個(gè)
函數(shù)的參數(shù)過多,會(huì)使得該函數(shù)易于受外部(其他部分的代碼)變化的影響,從而影響維護(hù)工作。函數(shù)的參數(shù)過多同時(shí)也會(huì)增大測(cè)試的工作量。
函數(shù)的參數(shù)個(gè)數(shù)不要超過5個(gè),如果超過了建議拆分為不同函數(shù)。
14、除打印類函數(shù)外,不要使用可變長(zhǎng)參函數(shù)。
可變長(zhǎng)參函數(shù)的處理過程比較復(fù)雜容易引入錯(cuò)誤,而且性能也比較低,使用過多的可變長(zhǎng)參函數(shù)將導(dǎo)致函數(shù)的維護(hù)難度大大增加。
15、在源文件范圍內(nèi)聲明和定義的所有函數(shù),除非外部可見,否則應(yīng)該增加static關(guān)鍵字
如果一個(gè)函數(shù)只是在同一文件中的其他地方調(diào)用,那么就用static聲明。使用static確保只是在聲明它的文件中是可見的,并且避免了和其他文件或庫中的相同標(biāo)識(shí)符發(fā)生混淆的可能性。
正確示例:建議定義一個(gè)STATIC宏,在調(diào)試階段,將STATIC定義為static,版本發(fā)布時(shí),改為空,以便于后續(xù)的打熱補(bǔ)丁等操作。
#ifdef _DEBUG
#define STATIC static
#else
#define STATIC
#endif
4 標(biāo)識(shí)符命名與定義
標(biāo)識(shí)符的命名規(guī)則歷來是一個(gè)敏感話題,典型的命名風(fēng)格如unix風(fēng)格、windows風(fēng)格等,從來無法達(dá)成共識(shí)。實(shí)際上,各種風(fēng)格都有其優(yōu)勢(shì)也有其劣勢(shì),而且往往和個(gè)人的審美觀有關(guān)。我們對(duì)標(biāo)識(shí)符定義主要是為了讓團(tuán)隊(duì)的代碼看起來盡可能統(tǒng)一,有利于代碼的后續(xù)閱讀和修改,產(chǎn)品可以根據(jù)自己的實(shí)際需要指定命名風(fēng)格,規(guī)范中不再做統(tǒng)一的規(guī)定。
1、標(biāo)識(shí)符的命名要清晰、明了,有明確含義,同時(shí)使用完整的單詞或大家基本可以理解的縮寫,避免使人產(chǎn)生誤解
盡可能給出描述性名稱,不要節(jié)約空間,讓別人很快理解你的代碼更重要。
正確示例:
int error_number;
int number_of_completed_connection;
錯(cuò)誤示例:
int n;
int nerr;
int n_comp_conns;
2、除了常見的通用縮寫以外,不使用單詞縮寫,不得使用漢語拼音
較短的單詞可通過去掉“元音”形成縮寫,較長(zhǎng)的單詞可取單詞的頭幾個(gè)字母形成縮寫,一些單詞有大家公認(rèn)的縮寫,常用單詞的縮寫必須統(tǒng)一。協(xié)議中的單詞的縮寫與協(xié)議保持一致。對(duì)于某個(gè)系統(tǒng)使用的專用縮寫應(yīng)該在注視或者某處做統(tǒng)一說明。
正確示例:一些常見可以縮寫的例子:
argument 可縮寫為 arg
buffer 可縮寫為 buff
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 可縮寫為 para
previous 可縮寫為 prev
register 可縮寫為 reg
semaphore 可縮寫為 sem
statistic 可縮寫為 stat
synchronize 可縮寫為 sync
temp 可縮寫為 tmp
3、產(chǎn)品/項(xiàng)目組內(nèi)部應(yīng)保持統(tǒng)一的命名風(fēng)格
Unix like和windows like風(fēng)格均有其擁躉,產(chǎn)品應(yīng)根據(jù)自己的部署平臺(tái),選擇其中一種,并在產(chǎn)品內(nèi)部保持一致。
4、用正確的反義詞組命名具有互斥意義的變量或相反動(dòng)作的函數(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/paste up/down
5、盡量避免名字中出現(xiàn)數(shù)字編號(hào),除非邏輯上的確需要編號(hào)
錯(cuò)誤示例:如下命名,使人產(chǎn)生疑惑。
#define EXAMPLE_0_TEST_
#define EXAMPLE_1_TEST_
正確示例:應(yīng)改為有意義的單詞命名。
#define EXAMPLE_UNIT_TEST_
#define EXAMPLE_ASSERT_TEST_
6、標(biāo)識(shí)符前不應(yīng)添加模塊、項(xiàng)目、產(chǎn)品、部門的名稱作為前綴
很多已有代碼中已經(jīng)習(xí)慣在文件名中增加模塊名,這種寫法類似匈牙利命名法,導(dǎo)致文件名不可讀,并且?guī)韼砣缦聠栴}:
第一眼看到的是模塊名,而不是真正的文件功能,阻礙閱讀;
文件名太長(zhǎng);
文件名和模塊綁定,不利于維護(hù)和移植。若foo.c進(jìn)行重構(gòu)后,從a模塊挪到b模塊,若foo.c
中有模塊名,則需要將文件名從a_module_foo.c改為b_module_foo.c。
7、平臺(tái)/ / 驅(qū)動(dòng)等適配代碼的標(biāo)識(shí)符命名風(fēng)格保持和平臺(tái)
涉及到外購(gòu)芯片以及配套的驅(qū)動(dòng),這部分的代碼變動(dòng)(包括為產(chǎn)品做適配的新增代碼),應(yīng)該保持原有的風(fēng)格。
8、重構(gòu)/修改部分代碼時(shí),應(yīng)保持和原有代碼的命名風(fēng)格一致
根據(jù)源代碼現(xiàn)有的風(fēng)格繼續(xù)編寫代碼,有利于保持總體一致。
9、文件命名統(tǒng)一采用小寫字符
因?yàn)椴煌到y(tǒng)對(duì)文件名大小寫處理會(huì)不同(如MS的DOS、Windows系統(tǒng)不區(qū)分大小寫,但是Linux系統(tǒng)則區(qū)分),所以代碼文件命名建議統(tǒng)一采用全小寫字母命名。
10、全局變量應(yīng)增加“g_” 前綴,靜態(tài)變量應(yīng)增加“s_”
首先,全局變量十分危險(xiǎn),通過前綴使得全局變量更加醒目,促使開發(fā)人員對(duì)這些變量的使用更加小心。
其次,從根本上說,應(yīng)當(dāng)盡量不使用全局變量,增加g_和s_前綴,會(huì)使得全局變量的名字顯得很丑陋,從而促使開發(fā)人員盡量少使用全局變量。
11、禁止使用單字節(jié)命名變量,但 允許 定義i 、j、k作為局部循環(huán)變量
12、 不建議使用匈牙利命名法
匈牙利命名法是一種編程時(shí)的命名規(guī)范。基本原則是:變量名=屬性+類型+對(duì)象描述。匈牙利命名法源于微軟,然而卻被很多人以訛傳訛的使用。而現(xiàn)在即使是微軟也不再推薦使用匈牙利命名法。歷來對(duì)匈牙利命名法的一大詬病,就是導(dǎo)致了變量名難以閱讀,這和本規(guī)范的指導(dǎo)思想也有沖突,所以本規(guī)范特意強(qiáng)調(diào),變量命名不應(yīng)采用匈牙利命名法,而應(yīng)該想法使變量名為一個(gè)有意義的詞或詞組,方便代碼的閱讀。
變量命名需要說明的是變量的含義,而不是變量的類型。在變量命名前增加類型說明,反而降低了變量的可讀性;更麻煩的問題是,如果修改了變量的類型定義,那么所有使用該變量的地方都需要修改。
13、使用名詞或者形容詞+名詞方式命名變量
14、函數(shù)命名應(yīng)以函數(shù)要執(zhí)行的動(dòng)作命名,一般采用動(dòng)詞或者動(dòng)詞+名詞的結(jié)構(gòu)
正確示例:找到當(dāng)前進(jìn)程的當(dāng)前目錄:
DWORD GetCurrentDirectory( DWORD BufferLength, LPTSTR Buffer );
15、函數(shù)指針除了前綴,其他按照函數(shù)的命名規(guī)則命名
16、對(duì)于數(shù)值或者字符串等等常量的定義,建議采用全大寫字母,單詞之間加下劃線“_”的方式命名(枚舉同樣建議使用此方式定義)
正確示例:
#define PI_ROUNDED 3.14
17、除了頭文件或編譯開關(guān)等特殊標(biāo)識(shí)定義,宏定義不能使用下劃線“_”開頭和結(jié)尾
一般來說,?_?開頭、結(jié)尾的宏都是一些內(nèi)部的定義,ISO/IEC 9899(俗稱C99)中有如下的描述(6.10.8 Predefined macro names):
None of these macro names (這里上面是一些內(nèi)部定義的宏的描述),nor the identifier defined,shall be the subject of a #define or a #undef preprocessing directive.Any other predefined macro names shall begin with a leading underscore fol lowedby an uppercase letter ora second underscore.
5 變量
1、一個(gè)變量只有一個(gè)功能,不能把一個(gè)變量用作多種用途
一個(gè)變量只用來表示一個(gè)特定功能,不能把一個(gè)變量作多種用途,即同一變量取值不同時(shí),其代表的意義也不同。
錯(cuò)誤示例:具有兩種功能的反例
WORD DelRelTimeQue( void )
{
WORD Locate;
Locate = 3;
Locate = DeleteFromQue(Locate); /* Locate具有兩種功能:位置和函數(shù)DeleteFromQue的返回值 */
return Locate;
}
正確做法:使用兩個(gè)變量
WORD DelRelTimeQue( void )
{
WORD Ret;
WORD Locate;
Locate = 3;
Ret = DeleteFromQue(Locate);
return Ret;
}
2、結(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)中。
錯(cuò)誤示例:如下結(jié)構(gòu)不太清晰、合理。
typedef struct STUDENT_STRU
{
unsigned char name[32]; /* student's name */
unsigned char age; /* student's age */
unsigned char sex; /* student's sex, as follows */
/* 0 - FEMALE; 1 - MALE */
unsigned char teacher_name[32]; /* the student teacher's name */
unsigned char teacher_sex; /* his teacher sex */
} STUDENT;
正確示例:若改為如下,會(huì)更合理些。
typedef struct TEACHER_STRU
{
unsigned char name[32]; /* teacher name */
unsigned char sex; /* teacher sex, as follows */
/* 0 - FEMALE; 1 - MALE */
unsigned int teacher_ind; /* teacher index */
} TEACHER;
typedef struct STUDENT_STRU
{
unsigned char name[32]; /* student's name */
unsigned char age; /* student's age */
unsigned char sex; /* student's sex, as follows */
/* 0 - FEMALE; 1 - MALE */
unsigned int teacher_ind; /* his teacher index */
} STUDENT;
3、不用或者少用全局變量
單個(gè)文件內(nèi)部可以使用static的全局變量,可以將其理解為類的私有成員變量。
全局變量應(yīng)該是模塊的私有數(shù)據(jù),不能作用對(duì)外的接口使用,使用static類型定義,可以有效防止外部文件的非正常訪問,建議定義一個(gè)STATIC宏,在調(diào)試階段,將STATIC定義為static,版本發(fā)布時(shí),改為空,以便于后續(xù)的打補(bǔ)丁等操作。
4、防止局部變量與全局變量同名
盡管局部變量和全局變量的作用域不同而不會(huì)發(fā)生語法錯(cuò)誤,但容易使人誤解。
5、通訊過程中使用的結(jié)構(gòu),必須注意字節(jié)序
通訊報(bào)文中,字節(jié)序是一個(gè)重要的問題,我司設(shè)備使用的CPU類型復(fù)雜多樣,大小端、32位/64位的處理器也都有,如果結(jié)構(gòu)會(huì)在報(bào)文交互過程中使用,必須考慮字節(jié)序問題。由于位域在不同字節(jié)序下,表現(xiàn)看起來差別更大,所以更需要注意對(duì)于這種跨平臺(tái)的交互,數(shù)據(jù)成員發(fā)送前,都應(yīng)該進(jìn)行主機(jī)序到網(wǎng)絡(luò)序的轉(zhuǎn)換;接收時(shí),也必須進(jìn)行網(wǎng)絡(luò)序到主機(jī)序的轉(zhuǎn)換。
6、嚴(yán)禁使用未經(jīng)初始化的變量作為右值
在首次使用前初始化變量,初始化的地方離使用的地方越近越好。
7、構(gòu)造僅有一個(gè)模塊或函數(shù)可以修改、創(chuàng)建,而其余有關(guān)模塊或函數(shù)只訪問的全局變量,防止多個(gè)不同模塊或函數(shù)都可以修改、創(chuàng)建同一全局變量的現(xiàn)象
降低全局變量耦合度。
8、使用面向接口編程思想,通過 API 訪問數(shù)據(jù):如果本模塊的數(shù)據(jù)需要對(duì)外部模塊開放 ,應(yīng)提供接口函數(shù)來設(shè)置、獲取,同時(shí)注意全局?jǐn)?shù)據(jù)的訪問互斥
避免直接暴露內(nèi)部數(shù)據(jù)給外部模型使用,是防止模塊間耦合最簡(jiǎn)單有效的方法。定義的接口應(yīng)該有比較明確的意義,比如一個(gè)風(fēng)扇管理功能模塊,有自動(dòng)和手動(dòng)工作模式,那么設(shè)置、查詢工作模塊就可以定義接口為SetFanWorkMode,GetFanWorkMode;查詢轉(zhuǎn)速就可以定義為GetFanSpeed;風(fēng)扇支持節(jié)能功能開關(guān),可以定義EnabletFanSavePower等。
9、明確全局變量的初始化順序,避免跨模塊的初始化依賴
系統(tǒng)啟動(dòng)階段,使用全局變量前,要考慮到該全局變量在什么時(shí)候初始化,使用全局變量和初始化全局變量,兩者之間的時(shí)序關(guān)系,誰先誰后,一定要分析清楚,不然后果往往是低級(jí)而又災(zāi)難性的。
10、盡量減少?zèng)]有必要的數(shù)據(jù)類型默認(rèn)轉(zhuǎn)換與強(qiáng)制轉(zhuǎn)換
當(dāng)進(jìn)行數(shù)據(jù)類型強(qiáng)制轉(zhuǎn)換時(shí),其數(shù)據(jù)的意義、轉(zhuǎn)換后的取值等都有可能發(fā)生變化,而這些細(xì)節(jié)若考慮不周,就很有可能留下隱患。
錯(cuò)誤示例:如下賦值,多數(shù)編譯器不產(chǎn)生告警,但值的含義還是稍有變化。
char ch;
unsigned short int exam;
ch = -1;
exam = ch; // 編譯器不產(chǎn)生告警,此時(shí)exam為0xFFFF。
6 宏、常量
1、用宏定義表達(dá)式時(shí),要使用完備的括號(hào)
因?yàn)楹曛皇呛?jiǎn)單的代碼替換,不會(huì)像函數(shù)一樣先將參數(shù)計(jì)算后,再傳遞。
錯(cuò)誤示例:如下定義的宏都存在一定的風(fēng)險(xiǎn)
#define RECTANGLE_AREA(a, b) a * b
#define RECTANGLE_AREA(a, b) (a * b)
#define RECTANGLE_AREA(a, b) (a) * (b)
正確示例:
#define RECTANGLE_AREA(a, b) ((a) * (b))
這是因?yàn)椋喝绻x #define RECTANGLE_AREA(a, b) a * b 或 #define RECTANGLE_AREA(a, b) (a * b)則 c/RECTANGLE_AREA(a, b) 將擴(kuò)展成 c/a * b , c 與 b 本應(yīng)該是除法運(yùn)算,結(jié)果變成了乘法運(yùn)算,造成錯(cuò)誤。
如果定義 #define RECTANGLE_AREA(a, b) (a * b)則 RECTANGLE_AREA(c + d, e + f) 將擴(kuò)展成:(c + d * e + f), d 與 e 先運(yùn)算,造成錯(cuò)誤。
2、將宏所定義的多條表達(dá)式放在大括號(hào)中
3、使用宏時(shí),不允許參數(shù)發(fā)生變化
錯(cuò)誤示例:
#define SQUARE(a) ((a) * (a))
int a = 5;
int b;
b = SQUARE(a++); // 結(jié)果:a = 7,即執(zhí)行了兩次增。
正確示例:
b = SQUARE(a);
a++; // 結(jié)果:a = 6,即只執(zhí)行了一次增。
同時(shí)也建議即使函數(shù)調(diào)用,也不要在參數(shù)中做變量變化操作,因?yàn)榭赡芤玫慕涌诤瘮?shù),在某個(gè)版本升級(jí)后,變成了一個(gè)兼容老版本所做的一個(gè)宏,結(jié)果可能不可預(yù)知。
4、不允許直接使用魔鬼數(shù)字
使用魔鬼數(shù)字的弊端:代碼難以理解;如果一個(gè)有含義的數(shù)字多處使用,一旦需要修改這個(gè)數(shù)值,代價(jià)慘重。
使用明確的物理狀態(tài)或物理意義的名稱能增加信息,并能提供單一的維護(hù)點(diǎn)。
解決途徑:對(duì)于局部使用的唯一含義的魔鬼數(shù)字,可以在代碼周圍增加說明注釋,也可以定義局部const變量,變量命名自注釋。對(duì)于廣泛使用的數(shù)字,必須定義const全局變量/宏;同樣變量/宏命名應(yīng)是自注釋的。0作為一個(gè)特殊的數(shù)字,作為一般默認(rèn)值使用沒有歧義時(shí),不用特別定義。
5、除非必要,應(yīng)盡可能使用函數(shù)代替宏
宏對(duì)比函數(shù),有一些明顯的缺點(diǎn):
宏缺乏類型檢查,不如函數(shù)調(diào)用檢查嚴(yán)格;
宏展開可能會(huì)產(chǎn)生意想不到的副作用,如#define SQUARE(a) (a) * (a)這樣的定義,如果是SQUARE(i++),就會(huì)導(dǎo)致i被加兩次;如果是函數(shù)調(diào)用double square(double a) {return a * a;}則不會(huì)有此副作用;
以宏形式寫的代碼難以調(diào)試難以打斷點(diǎn),不利于定位問題;
宏如果調(diào)用的很多,會(huì)造成代碼空間的浪費(fèi),不如函數(shù)空間效率高。
錯(cuò)誤示例:下面的代碼無法得到想要的結(jié)果:
#define MAX_MACRO(a, b) ((a) > (b) ? (a) : (b))
int MAX_FUNC(int a, int b) {
return ((a) > (b) ? (a) : (b));
}
int testFunc()
{
unsigned int a = 1;
int b = -1;
printf("MACRO: max of a and b is: %d ", MAX_MACRO(++a, b));
printf("FUNC : max of a and b is: %d ", MAX_FUNC(a, b));
return 0;
}
上面宏代碼調(diào)用中,由于宏缺乏類型檢查,a和b的比較變成無符號(hào)數(shù)的比較,結(jié)果是a < b,所以a只加了一次,所以最終的輸出結(jié)果是:
MACRO: max of a and b is: -1
FUNC : max of a and b is: 2
6、常量建議使用 const 定義代替宏
“盡量用編譯器而不用預(yù)處理”,因?yàn)?define經(jīng)常被認(rèn)為好象不是語言本身的一部分。看下面的語句:
#define ASPECT_RATIO 1.653
編譯器會(huì)永遠(yuǎn)也看不到ASPECT_RATIO這個(gè)符號(hào)名,因?yàn)樵谠创a進(jìn)入編譯器之前,它會(huì)被預(yù)處理程序去掉,于是ASPECT_RATIO不會(huì)加入到符號(hào)列表中。如果涉及到這個(gè)常量的代碼在編譯時(shí)報(bào)錯(cuò),就會(huì)很令人費(fèi)解,因?yàn)閳?bào)錯(cuò)信息指的是1.653,而不是ASPECT_RATIO。如果ASPECT_RATIO不是在你自己寫的頭文件中定義的,你就會(huì)奇怪1.653是從哪里來的,甚至?xí)〞r(shí)間跟蹤下去。這個(gè)問題也會(huì)出現(xiàn)在符號(hào)調(diào)試器中,因?yàn)橥瑯拥兀闼鶎懙姆?hào)名不會(huì)出現(xiàn)在符號(hào)列表中。
解決這個(gè)問題的方案很簡(jiǎn)單:不用預(yù)處理宏,定義一個(gè)常量:
const double ASPECT_RATIO = 1.653;
這種方法很有效,但有兩個(gè)特殊情況要注意。首先,定義指針常量時(shí)會(huì)有點(diǎn)不同。因?yàn)槌A慷x一般是放在頭文件中(許多源文件會(huì)包含它),除了指針?biāo)傅念愋鸵x成const外,重要的是指針也經(jīng)常要定義成const。例如,要在頭文件中定義一個(gè)基于char*的字符串常量,你要寫兩次const:
const char * const authorName = "Scott Meyers";
延伸閱讀材料:關(guān)于const和指針的使用,這里摘錄兩段ISO/IEC 9899(俗稱C99)的描述:
7、宏定義中盡量不使用 return 、 goto 、 continue 、 break等改變程序流程的語句
如果在宏定義中使用這些改變流程的語句,很容易引起資源泄漏問題,使用者很難自己察覺。
錯(cuò)誤示例:在某頭文件中定義宏CHECK_AND_RETURN:
#define CHECK_AND_RETURN(cond, ret) {if (cond == NULL_PTR) {return ret;}}
//然后在某函數(shù)中使用(只說明問題,代碼并不完整):
pMem1 = VOS_MemAlloc(...);
CHECK_AND_RETURN(pMem1 , ERR_CODE_XXX)
pMem2 = VOS_MemAlloc(...);
CHECK_AND_RETURN(pMem2 , ERR_CODE_XXX) /*此時(shí)如果pMem2==NULL_PTR,則pMem1未釋放函數(shù)就返回了,造成內(nèi)存泄漏。*/
所以說,類似于CHECK_AND_RETURN這些宏,雖然能使代碼簡(jiǎn)潔,但是隱患很大,使用須謹(jǐn)慎。
7 表達(dá)式
1、表達(dá)式的值在標(biāo)準(zhǔn)所允許的任何運(yùn)算次序下都應(yīng)該是相同的
2、函數(shù)調(diào)用不要作為另一個(gè)函數(shù)的參數(shù)使用,否則對(duì)于代碼的調(diào)試、閱讀都不利
錯(cuò)誤示例:如下代碼不合理,僅用于說明當(dāng)函數(shù)作為參數(shù)時(shí),由于參數(shù)壓棧次數(shù)不是代碼可以控制的,可能造成未知的輸出:
int g_var;
int fun1()
{
g_var += 10;
return g_var;
}
int fun2()
{
g_var += 100;
return g_var;
}
int main(int argc, char *argv[], char *envp[])
{
g_var = 1;
printf("func1: %d, func2: %d ", fun1(), fun2());
g_var = 1;
printf("func2: %d, func1: %d ", fun2(), fun1());
}
上面的代碼,使用斷點(diǎn)調(diào)試起來也比較麻煩,閱讀起來也不舒服,所以不要為了節(jié)約代碼行,而寫這種代碼。
3、賦值語句不要寫在 if 等語句中,或者作為函數(shù)的參數(shù)使用
因?yàn)閕f語句中,會(huì)根據(jù)條件依次判斷,如果前一個(gè)條件已經(jīng)可以判定整個(gè)條件,則后續(xù)條件語句不會(huì)再運(yùn)行,所以可能導(dǎo)致期望的部分賦值沒有得到運(yùn)行。
錯(cuò)誤示例:
int main(int argc, char *argv[], char *envp[])
{
int a = 0;
int b;
if ((a == 0) || ((b = fun1()) > 10))
{
printf("a: %d ", a);
}
printf("b: %d ", b);
}
作用函數(shù)參數(shù)來使用,參數(shù)的壓棧順序不同可能導(dǎo)致結(jié)果未知。
4、用括號(hào)明確表達(dá)式的操作順序,避免過分依賴默認(rèn)優(yōu)先級(jí)
使用括號(hào)強(qiáng)調(diào)所使用的操作符,防止因默認(rèn)的優(yōu)先級(jí)與設(shè)計(jì)思想不符而導(dǎo)致程序出錯(cuò);同時(shí)使得代碼更為清晰可讀,然而過多的括號(hào)會(huì)分散代碼使其降低了可讀性。
5、賦值操作符不能使用在產(chǎn)生布爾值的表達(dá)式上
示例:
x = y;
if (x != 0)
{
foo ();
}
不能寫成:
if (( x = y ) != 0)
{
foo ();
}
或者更壞的:
if (x = y)
{
foo ();
}
8 注釋
1、優(yōu)秀的代碼可 以自我解釋,不通過注釋即可輕易讀懂
優(yōu)秀的代碼不寫注釋也可輕易讀懂,注釋無法把糟糕的代碼變好,需要很多注釋來解釋的代碼往往存在壞味道,需要重構(gòu)。
錯(cuò)誤示例:注釋不能消除代碼的壞味道:
/* 判斷m是否為素?cái)?shù)*/
/* 返回值:: 是素?cái)?shù),: 不是素?cái)?shù)*/
int p(int m)
{
int k = sqrt(m);
for (int i = 2; i <= k; i++)
if (m % i == 0)
break; /* 發(fā)現(xiàn)整除,表示m不為素?cái)?shù),結(jié)束遍歷*/
/* 遍歷中沒有發(fā)現(xiàn)整除的情況,返回*/
if (i > k)
return 1;
/* 遍歷中沒有發(fā)現(xiàn)整除的情況,返回*/
else
return 0;
}
重構(gòu)代碼后,不需要注釋:
int IsPrimeNumber(int num)
{
int sqrt_of_num = sqrt (num);
for (int i = 2; i <= sqrt_of_num; i++)
{
if (num % i == 0)
{
return FALSE;
}
}
return TRUE;
}
2、注釋的內(nèi)容要清楚、明了,含義準(zhǔn)確,防止注釋二義性
有歧義的注釋反而會(huì)導(dǎo)致維護(hù)者更難看懂代碼,正如帶兩塊表反而不知道準(zhǔn)確時(shí)間。
3、在代碼的功能、意圖層次上進(jìn)行注釋,即注釋解釋 代碼難以直接表達(dá)的意圖 , 而不是重復(fù)描述代碼
注釋的目的是解釋代碼的目的、功能和采用的方法,提供代碼以外的信息,幫助讀者理解代碼,防止沒必要的重復(fù)注釋信息。對(duì)于實(shí)現(xiàn)代碼中巧妙的、晦澀的、有趣的、重要的地方加以注釋。注釋不是為了名詞解釋(what),而是說明用途(why)。
4、修改代碼時(shí),維護(hù)代碼周邊的所有注釋,以保證注釋與代碼的一致性,不再有用的注釋要?jiǎng)h除
不要將無用的代碼留在注釋中,隨時(shí)可以從源代碼配置庫中找回代碼;即使只是想暫時(shí)排除代碼,也要留個(gè)標(biāo)注,不然可能會(huì)忘記處理它。
5、文件頭部應(yīng)進(jìn)行注釋,注釋必須列出:版權(quán)說明、版本號(hào)、生成日期、作者姓名、工號(hào)、內(nèi)容、功能說明、與其它文件的關(guān)系、修改日志等,頭文件的注釋中還應(yīng)有函數(shù)功能簡(jiǎn)要說明
正確示例:下面這段頭文件的頭注釋比較標(biāo)準(zhǔn),當(dāng)然,并不局限于此格式,但上述信息建議要包含在內(nèi)。
6、函數(shù)聲明處注釋描述函數(shù)功能、性能及用法,包括輸入和輸出參數(shù)、函數(shù)返回值、可重入的要求等;定義處詳細(xì)描述函數(shù)功能和實(shí)現(xiàn)要點(diǎn),如實(shí)現(xiàn)的簡(jiǎn)要步驟、實(shí)現(xiàn)的理由、 設(shè)計(jì)約束等
重要的、復(fù)雜的函數(shù),提供外部使用的接口函數(shù)應(yīng)編寫詳細(xì)的注釋。
7、全局變量要有較詳細(xì)的注釋,包括對(duì)其功能、取值范圍以及存取時(shí)注意事項(xiàng)等的說明
正確示例:
/* The ErrorCode when SCCP translate */
/* Global Title failure, as follows */ /* 變量作用、含義*/
/* 0 -SUCCESS 1 -GT Table error */
/* 2 -GT error Others -no use */ /* 變量取值范圍*/
/* only function SCCPTranslate() in */
/* this modual can modify it, and other */
/* module can visit it through call */
/* the function GetGTTransErrorCode() */ /* 使用方法*/
BYTE g_GTTranErrorCode;
8、注釋應(yīng)放在其代碼上方相鄰位置或右方,不可放在下面,如放于上方則需與其上面的代碼用空行隔開,且與下方代碼縮進(jìn)相同
正確示例:
/* active statistic task number */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */
可按如下形式說明枚舉/數(shù)據(jù)/聯(lián)合結(jié)構(gòu)。
/* sccp interface with sccp user primitive message name */
enum SCCP_USER_PRIMITIVE
{
N_UNITDATA_IND, /* sccp notify sccp user unit data come */
N_NOTICE_IND, /* sccp notify user the No.7 network can not transmission this message */
N_UNITDATA_REQ, /* sccp user's unit data transmission request*/
};
9、對(duì)于 switch語句下的case語句,如果因?yàn)樘厥馇闆r需要處理完一個(gè)case后進(jìn)入下一個(gè)case處理,必須在該case語句處理完、下一個(gè)case語句前加上明確的注釋
這樣比較清楚程序編寫者的意圖,有效防止無故遺漏break語句。
case CMD_FWD:
ProcessFwd();
/* now jump into c ase CMD_A */
case CMD_A:
ProcessA();
break;
//對(duì)于中間無處理的連續(xù)case,已能較清晰說明意圖,不強(qiáng)制注釋。
switch (cmd_flag)
{
case CMD_A:
case CMD_B:
{
ProcessCMD();
break;
}
……
}
10、避免在注釋中使用縮寫,除非是業(yè)界通用或子系統(tǒng)內(nèi)標(biāo)準(zhǔn)化的縮寫
11、同一產(chǎn)品或項(xiàng)目組統(tǒng)一注釋風(fēng)格
12、避免在一行代碼或表達(dá)式的中間插入注釋
除非必要,不應(yīng)在代碼或表達(dá)中間插入注釋,否則容易使代碼可理解性變差
13、注釋應(yīng)考慮程序易讀及外觀排版的因素,使用的語言若是中、英兼有的,建議多使用中文,除非能用非常流利準(zhǔn)確的英文表達(dá),對(duì)于有外籍員工的,由產(chǎn)品確定注釋語言
注釋語言不統(tǒng)一,影響程序易讀性和外觀排版,出于對(duì)維護(hù)人員的考慮,建議使用中文。
14、文件頭、函數(shù)頭、全局常量變量、類型定義的注釋格式采用工具可識(shí)別的格式
采用工具可識(shí)別的注釋格式,例如doxygen格式,方便工具導(dǎo)出注釋形成幫助文檔。以doxygen格式為例,文件頭,函數(shù)和全部變量的注釋的示例如下:
9 排版與格式
1、程序塊采用縮進(jìn)風(fēng)格編寫, 每級(jí)縮進(jìn)為4個(gè)空格
2、相對(duì)獨(dú)立的程序塊之間、變量說明之后必須加空行
錯(cuò)誤示例:如下例子不符合規(guī)范。
if (!valid_ni(ni))
{
// program code
...
}
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
正確示例:
if (!valid_ni(ni))
{
// program code
...
}
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
3、一條語句不能過長(zhǎng),如不能拆分需要分行寫。一行到底多少字符換行比較合適,產(chǎn)品可以自行確定
對(duì)于目前大多數(shù)的PC來說,132比較合適(80/132是VTY常見的行寬值);對(duì)于新PC寬屏顯示器較多的產(chǎn)品來說,可以設(shè)置更大的值。換行時(shí)有如下建議:
換行時(shí)要增加一級(jí)縮進(jìn),使代碼可讀性更好;
低優(yōu)先級(jí)操作符處劃分新行;換行時(shí)操作符應(yīng)該也放下來,放在新行首;
換行時(shí)建議一個(gè)完整的語句放在一行,不要根據(jù)字符數(shù)斷行。
正確示例:
if ((temp_flag_var == TEST_FLAG)
&&(((temp_counter_var - TEST_COUNT_BEGIN) % TEST_COUNT_MODULE) >= TEST_COUNT_THRESHOLD))
{
// process code
}
4、多個(gè)短語句(包括賦值語句)不允許寫在同一行內(nèi) ,即一行只寫一條語句
錯(cuò)誤示例:
int a = 5; int b= 10; //不好的排版
正確示例:
int a = 5;
int b= 10;
5、if 、 for 、 do 、 while 、 case 、 switch 、 default 等語句獨(dú)占一行
執(zhí)行語句必須用縮進(jìn)風(fēng)格寫,屬于if、for、do、while、case、switch、default等下一個(gè)縮進(jìn)級(jí)別;
一般寫if、for、do、while等語句都會(huì)有成對(duì)出現(xiàn)的?{}?,對(duì)此有如下建議可以參考:if、for、do、while等語句后的執(zhí)行語句建議增加成對(duì)的“{}”;如果if/else配套語句中有一個(gè)分支有“{}”,那么另一個(gè)分支即使一行代碼也建議增加“{}”;添加“{”的位置可以在if等語句后,也可以獨(dú)立占下一行;獨(dú)立占下一行時(shí),可以和if在一個(gè)縮進(jìn)級(jí)別,也可以在下一個(gè)縮進(jìn)級(jí)別;但是如果if語句很長(zhǎng),或者已經(jīng)有換行,建議“{”使用獨(dú)占一行的寫法。
6、在兩個(gè)以上的關(guān)鍵字、變量、常量進(jìn)行對(duì)等操作時(shí),它們之間的操作符之前、之后或者前后要加空格 ;進(jìn)行非對(duì)等操作時(shí),如果是關(guān)系密切的立即操作符(如-> > ),后不應(yīng)加空格
采用這種松散方式編寫代碼的目的是使代碼更加清晰。
在已經(jīng)非常清晰的語句中沒有必要再留空格,如括號(hào)內(nèi)側(cè)(即左括號(hào)后面和右括號(hào)前面)不需要加空格,多重括號(hào)間不必加空格,因?yàn)樵贑語言中括號(hào)已經(jīng)是最清晰的標(biāo)志了。在長(zhǎng)語句中,如果需要加的空格非常多,那么應(yīng)該保持整體清晰,而在局部不加空格。給操作符留空格時(shí)不要連續(xù)留兩個(gè)以上空格。
正確示例:
1、逗號(hào)、分號(hào)只在后面加空格。
int a, b, c;
2、比較操作符, 賦值操作符"="、 "+=",算術(shù)操作符"+"、"%",邏輯操作符"&&"、"&",位域操作符"<<"、"^"等雙目操作符的前后加空格。?
if (current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;
3、"!"、"~"、"++"、"--"、"&"(地址操作符)等單目操作符前后不加空格。
*p = 'a'; // 內(nèi)容操作"*"與內(nèi)容之間
flag = !is_empty; // 非操作"!"與內(nèi)容之間
p = &mem; // 地址操作"&" 與內(nèi)容之間
i++;
4、"->"、"."前后不加空格。
p->id = pid; // "->"指針前后不加空格
5、if、for、while、switch等與后面的括號(hào)間應(yīng)加空格,使if等關(guān)鍵字更為突出、明顯。
if (a >= b && c > d)
7、注釋符(包括/**/、//)與注釋內(nèi)容之間要用一個(gè)空格進(jìn)行分隔
8、源程序中關(guān)系較為緊密的代碼應(yīng)盡可能相鄰
10 代碼編輯編譯
1、使用編譯器的最高告警級(jí)別,理解所有的告警,通過修改代碼而不是降低告警級(jí)別來消除所有告警
編譯器是你的朋友,如果它發(fā)出某個(gè)告警,這經(jīng)常說明你的代碼中存在潛在的問題。
2、在產(chǎn)品軟件(項(xiàng)目組)中,要統(tǒng)一編譯開關(guān)、靜態(tài)檢查選項(xiàng)以及相應(yīng)告警清除策略
如果必須禁用某個(gè)告警,應(yīng)盡可能單獨(dú)局部禁用,并且編寫一個(gè)清晰的注釋,說明為什么屏蔽。某些語句經(jīng)編譯/靜態(tài)檢查產(chǎn)生告警,但如果你認(rèn)為它是正確的,那么應(yīng)通過某種手段去掉告警信息。
4、本地構(gòu)建工具(如 PC-Lint)的配置應(yīng)該和持續(xù)集成的一致
兩者一致,避免經(jīng)過本地構(gòu)建的代碼在持續(xù)集成上構(gòu)建失敗
5、 使用版本控制(配置管理)系統(tǒng),及時(shí)簽入通過本地構(gòu)建的代碼,確保簽入的代碼不會(huì)影響構(gòu)建成功
及時(shí)簽入代碼降低集成難度。
6、要小心地使用編輯器提供的塊拷貝功能編程
審核編輯 :李倩
-
C語言
+關(guān)注
關(guān)注
180文章
7605瀏覽量
136993 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4333瀏覽量
62686 -
編輯器
+關(guān)注
關(guān)注
1文章
806瀏覽量
31190
原文標(biāo)題:萬字 | 菊花廠C語言編程10大規(guī)范
文章出處:【微信號(hào):?jiǎn)纹瑱C(jī)與嵌入式,微信公眾號(hào):?jiǎn)纹瑱C(jī)與嵌入式】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論