1. 程式人生 > >華為C語言程式設計規範(整理)

華為C語言程式設計規範(整理)

總體原則
1、清晰第一
2、簡潔為美
3、選擇合適的風格,與程式碼原有風格保持一致

1 標頭檔案

對於C語言來說,標頭檔案的設計體現了大部分的系統設計。
原則1.1 標頭檔案中適合放置介面的宣告,不適合放置實現。
說明:標頭檔案是模組(Module)或單元(Unit)的對外介面。標頭檔案中應放置對外部的宣告,如對外提供的函式宣告、巨集定義、型別定義等。

原則1.2 標頭檔案應當職責單一。
說明:標頭檔案過於複雜,依賴過於複雜是導致編譯時間過長的主要原因。很多現有程式碼中標頭檔案過大,職責過多,再加上迴圈依賴的問題,可能導致為了在.c中使用一個巨集,而包含十幾個標頭檔案。

原則1.3 標頭檔案應向穩定的方向包含。


說明:標頭檔案的包含關係是一種依賴,一般來說,應當讓不穩定的模組依賴穩定的模組,從而當不穩定的模組發生變化時,不會影響(編譯)穩定的模組。
產品依賴於平臺,平臺依賴於標準庫。
除了不穩定的模組依賴於穩定的模組外,更好的方式是兩個模組共同依賴於介面。

規則1.1 每一個.c檔案應有一個同名.h檔案,用於宣告需要對外公開的介面。
說明:如果一個.c檔案不需要對外公佈任何介面,則其就不應當存在,除非它是程式的入口,如main函式所在的檔案。

規則1.2 禁止標頭檔案迴圈依賴。
說明:標頭檔案迴圈依賴,指a.h包含b.h,b.h包含c.h,c.h包含a.h之類導致任何一個頭檔案修改,都導致所有包含了a.h/b.h/c.h的程式碼全部重新編譯一遍。而如果是單向依賴,如a.h包含b.h,b.h包含c.h,而c.h不包含任何標頭檔案,則修改a.h不會導致包含了b.h/c.h的原始碼重新編譯。

規則1.3 .c/.h檔案禁止包含用不到的標頭檔案。
說明:很多系統中標頭檔案包含關係複雜,開發人員為了省事起見,可能不會去一一鑽研,直接包含一切想到的標頭檔案,甚至有些產品乾脆釋出了一個god.h,其中包含了所有標頭檔案,然後釋出給各個專案組使用,這種只圖一時省事的做法,導致整個系統的編譯時間進一步惡化,並對後來人的維護造成了巨大的麻煩。

規則1.4 標頭檔案應當自包含。
說明:簡單的說,自包含就是任意一個頭檔案均可獨立編譯。如果一個檔案包含某個標頭檔案,還要包含另外一個頭檔案才能工作的話,就會增加交流障礙,給這個標頭檔案的使用者增添不必要的負擔。

規則1.5 總是編寫內部#include保護符(#define 保護)。


說明:多次包含一個頭檔案可以通過認真的設計來避免。如果不能做到這一點,就需要採取阻止標頭檔案內容被包含多於一次的機制。

規則1.6 禁止在標頭檔案中定義變數。
說明:在標頭檔案中定義變數,將會由於標頭檔案被其他.c檔案包含而導致變數重複定義。

規則1.7 只能通過包含標頭檔案的方式使用其他.c提供的介面,禁止在.c中通過extern的方式使用外部函式介面、變數。
說明:若a.c使用了b.c定義的foo()函式,則應當在b.h中宣告extern int foo(int input);並在a.c中通過#include

2 函式

函式設計的精髓:編寫整潔函式,同時把程式碼有效組織起來。
原則2.1 一個函式僅完成一件功能。
說明:一個函式實現多個功能給開發、使用、維護都帶來很大的困難。

原則2.2 重複程式碼應該儘可能提煉成函式。
說明:重複程式碼提煉成函式可以帶來維護成本的降低。

規則2.1 避免函式過長,新增函式不超過50行(非空非註釋行)。
說明:本規則僅對新增函式做要求,對已有函式修改時,建議不增加程式碼行。

規則2.2 避免函式的程式碼塊巢狀過深,新增函式的程式碼塊巢狀不超過4層。
說明:本規則僅對新增函式做要求,對已有的程式碼建議不增加巢狀層次。

規則2.3 可重入函式應避免使用共享變數;若需要使用,則應通過互斥手段(關中斷、訊號量)對其加以保護。
說明:可重入函式是指可能被多個任務併發呼叫的函式。在多工作業系統中,函式具有可重入性是多個任務可以共用此函式的必要條件。共享變數指的全域性變數和static變數。

規則2.4 對引數的合法性檢查,由呼叫者負責還是由介面函式負責,應在專案組/模組內應統一規定。預設由呼叫者負責。
說明:對於模組間介面函式的引數的合法性檢查這一問題,往往有兩個極端現象,即:要麼是呼叫者和被呼叫者對引數均不作合法性檢查,結果就遺漏了合法性檢查這一必要的處理過程,造成問題隱患;要麼就是呼叫者和被呼叫者均對引數進行合法性檢查,這種情況雖不會造成問題,但產生了冗餘程式碼,降低了效率。

規則2.5 對函式的錯誤返回碼要全面處理。
說明:一個函式(標準庫中的函式/第三方庫函式/使用者定義的函式)能夠提供一些指示錯誤發生的方法。這可以通過使用錯誤標記、特殊的返回資料或者其他手段,不管什麼時候函式提供了這樣的機制,呼叫程式應該在函式返回時立刻檢查錯誤指示。

規則2.6 設計高扇入,合理扇出(小於7)的函式。
說明:扇出是指一個函式直接呼叫(控制)其它函式的數目,而扇入是指有多少上級函式呼叫它。

規則2.7 廢棄程式碼(沒有被呼叫的函式和變數)要及時清除。
說明:程式中的廢棄程式碼不僅佔用額外的空間,而且還常常影響程式的功能與效能,很可能給程式的測試、維護等造成不必要的麻煩。

建議2.1 函式不變引數使用const。
說明:不變的值更易於理解/跟蹤和分析,把const作為預設選項,在編譯時會對其進行檢查,使程式碼更牢固/更安全。

建議2.2 函式應避免使用全域性變數、靜態區域性變數和I/O操作,不可避免的地方應集中使用。
說明:帶有內部“儲存器”的函式的功能可能是不可預測的,因為它的輸出可能取決於內部儲存器(如某標記)的狀態。這樣的函式既不易於理解又不利於測試和維護。在C語言中,函式的static區域性變數是函式的內部儲存器,有可能使函式的功能不可預測,然而,當某函式的返回值為指標型別時,則必須是static的區域性變數的地址作為返回值,若為auto類,則返回為錯針。

建議2.3 檢查函式所有非引數輸入的有效性,如資料檔案、公共變數等。
說明:函式的輸入主要有兩種:一種是引數輸入;另一種是全域性變數、資料檔案的輸入,即非引數輸入。函式在使用輸入引數之前,應進行有效性檢查。

建議2.4 函式的引數個數不超過5個。
說明:函式的引數過多,會使得該函式易於受外部(其他部分的程式碼)變化的影響,從而影響維護工作。函式的引數過多同時也會增大測試的工作量。

建議2.5 除列印類函式外,不要使用可變長參函式。
說明:可變長參函式的處理過程比較複雜容易引入錯誤,而且效能也比較低,使用過多的可變長參函式將導致函式的維護難度大大增加。

建議2.6 在原始檔範圍內宣告和定義的所有函式,除非外部可見,否則應該增加static關鍵字。
說明:如果一個函式只是在同一檔案中的其他地方呼叫,那麼就用static宣告。使用static確保只是在宣告它的檔案中是可見的,並且避免了和其他檔案或庫中的相同識別符號發生混淆的可能性。

3 識別符號命名與定義

unix like風格:單詞用小寫字母,每個單詞直接用下劃線“_”分割。
Windows風格:大小寫字母混用,單詞連在一起,每個單詞首字母大寫。
原則3.1 識別符號的命名要清晰、明瞭,有明確含義,同時使用完整的單詞或大家基本可以理解的縮寫,避免使人產生誤解。
說明:儘可能給出描述性名稱,不要節約空間,讓別人很快理解你的程式碼更重要。

原則3.2 除了常見的通用縮寫以外,不使用單詞縮寫,不得使用漢語拼音。
說明:較短的單詞可通過去掉“母音”形成縮寫,較長的單詞可取單詞的頭幾個字母形成縮寫,一些
單詞有大家公認的縮寫,常用單詞的縮寫必須統一。協議中的單詞的縮寫與協議保持一致。對於某個

規則3.1 產品/專案組內部應保持統一的命名風格。
說明:Unix like和windows like風格均有其擁躉,產品應根據自己的部署平臺,選擇其中一種,並在產品內部保持一致。

規則3.2 全域性變數應增加“g_”字首。

規則3.3 靜態變數應增加“s_”字首。
說明:增加g字首或者s字首,原因如下:首先,全域性變數十分危險,通過字首使得全域性變數更加醒目,促使開發人員對這些變數的使用更加小心。其次,從根本上說,應當儘量不使用全域性變數,增加g_和s_字首,會使得全域性變數的名字顯得很醜陋,從而促使開發人員儘量少使用全域性變數。

規則3.4 禁止使用單位元組命名變數,但允許定義i、j、k作為區域性迴圈變數。

規則3.5 對於數值或者字串等等常量的定義,建議採用全大寫字母,單詞之間加下劃線„_‟的方式命名(列舉同樣建議使用此方式定義)。

規則3.6 除了標頭檔案或編譯開關等特殊標識定義,巨集定義不能使用下劃線„_‟開頭和結尾。
說明:一般來說,‟_‟開頭、結尾的巨集都是一些內部的定義,

建議3.1 用正確的反義片語命名具有互斥意義的變數或相反動作的函式等。

建議3.2 儘量避免名字中出現數字編號,除非邏輯上的確需要編號。

建議3.3 識別符號前不應新增模組、專案、產品、部門的名稱作為字首。
說明:很多已有程式碼中已經習慣在檔名中增加模組名,這種寫法類似匈牙利命名法,導致檔名不
可讀,,不利於維護和移植。

建議3.4 平臺/驅動等適配程式碼的識別符號命名風格保持和平臺/驅動一致。
說明:涉及到外購晶片以及配套的驅動,這部分的程式碼變動(包括為產品做適配的新增程式碼),應該
保持原有的風格。

建議3.5 重構/修改部分程式碼時,應保持和原有程式碼的命名風格一致。
說明:根據原始碼現有的風格繼續編寫程式碼,有利於保持總體一致。

建議3.6 檔案命名統一採用小寫字元。
說明:因為不同系統對檔名大小寫處理會不同(如MS的DOS、Windows系統不區分大小寫,但是Linux
系統則區分),所以程式碼檔案命名建議統一採用全小寫字母命名。

建議3.7 不建議使用匈牙利命名法。
說明:變數命名需要說明的是變數的含義,而不是變數的型別。在變數命名前增加型別說明,反而降低了變數的可讀性;更麻煩的問題是,如果修改了變數的型別定義,那麼所有使用該變數的地方都需要修改。

建議3.8 使用名詞或者形容詞+名詞方式命名變數。

建議3.9 函式命名應以函式要執行的動作命名,一般採用動詞或者動詞+名詞的結構。

建議3.10 函式指標除了字首,其他按照函式的命名規則命名。

4 變數

原則4.1 一個變數只有一個功能,不能把一個變數用作多種用途。
說明:一個變數只用來表示一個特定功能,不能把一個變數作多種用途,即同一變數取值不同時,其代表的意義也不同。

原則4.2 結構功能單一;不要設計面面俱到的資料結構。
說明:相關的一組資訊才是構成一個結構體的基礎,結構的定義應該可以明確的描述一個物件,而不是一組相關性不強的資料的集合。

原則4.3 不用或者少用全域性變數。
說明:單個檔案內部可以使用static的全域性變數,可以將其理解為類的私有成員變數。

規則4.1 防止區域性變數與全域性變數同名。
說明:儘管區域性變數和全域性變數的作用域不同而不會發生語法錯誤,但容易使人誤解。

規則4.2 通訊過程中使用的結構,必須注意位元組序。
說明:通訊報文中,位元組序是一個重要的問題,我司裝置使用的cpu型別複雜多樣,大小端、32位/64位的處理器也都有,如果結構會在報文互動過程中使用,必須考慮位元組序問題。

規則4.3 嚴禁使用未經初始化的變數作為右值。
說明:堅持建議4.3(在首次使用前初始化變數,初始化的地方離使用的地方越近越好。)可以有效避免未初始化錯誤。

建議4.1 構造僅有一個模組或函式可以修改、建立,而其餘有關模組或函式只訪問的全域性變數,防止多個不同模組或函式都可以修改、建立同一全域性變數的現象。
說明:降低全域性變數耦合度。

建議4.2 使用面向介面程式設計思想,通過API訪問資料:如果本模組的資料需要對外部模組開放,應提供介面函式來設定、獲取,同時注意全域性資料的訪問互斥。
說明:避免直接暴露內部資料給外部模型使用,是防止模組間耦合最簡單有效的方法。

建議4.3 在首次使用前初始化變數,初始化的地方離使用的地方越近越好。
說明:未初始化變數是C和C++程式中錯誤的常見來源。在變數首次使用前確保正確初始化。
在較好的方案中,變數的定義和初始化要做到親密無間。

建議4.4 明確全域性變數的初始化順序,避免跨模組的初始化依賴。
說明:系統啟動階段,使用全域性變數前,要考慮到該全域性變數在什麼時候初始化,使用全域性變數和初始化全域性變數,兩者之間的時序關係,誰先誰後,一定要分析清楚,不然後果往往是低階而又災難性的。

建議4.5 儘量減少沒有必要的資料型別預設轉換與強制轉換。
說明:當進行資料型別強制轉換時,其資料的意義、轉換後的取值等都有可能發生變化,而這些細節若考慮不周,就很有可能留下隱患。

5 巨集、常量

規則5.1 用巨集定義表示式時,要使用完備的括號。
說明:因為巨集只是簡單的程式碼替換,不會像函式一樣先將引數計算後,再傳遞。

規則5.2 將巨集所定義的多條表示式放在大括號中。
說明:更好的方法是多條語句寫成do while(0)的方式。

規則5.3 使用巨集時,不允許引數發生變化。

規則5.4 不允許直接使用魔鬼數字。
說明:使用魔鬼數字的弊端:程式碼難以理解;如果一個有含義的數字多處使用,一旦需要修改這個數值,代價慘重。

建議5.1 除非必要,應儘可能使用函式代替巨集。
說明:巨集對比函式,有一些明顯的缺點:1.巨集缺乏型別檢查,不如函式呼叫檢查嚴格。2.巨集展開可能會產生意想不到的副作用,如#define SQUARE(a) (a) * (a)這樣的定義,如果是SQUARE(i++),就會導致i被加兩次;如果是函式呼叫double square(double a) {return a * a;}則不會有此副作用。3.以巨集形式寫的程式碼難以除錯難以打斷點,不利於定位問題。4.巨集如果呼叫的很多,會造成程式碼空間的浪費,不如函式空間效率高。

建議5.2 常量建議使用const定義代替巨集。
說明:儘量用編譯器而不用預處理,因為#define經常被認為好象不是語言本身的一部分。

建議5.3 巨集定義中儘量不使用return、goto、continue、break等改變程式流程的語句。
說明:如果在巨集定義中使用這些改變流程的語句,很容易引起資源洩漏問題,使用者很難自己察覺。

6 質量保證

原則6.1 程式碼質量保證優先原則
(1)正確性,指程式要實現設計要求的功能。
(2)簡潔性,指程式易於理解並且易於實現。
(3)可維護性,指程式被修改的能力,包括糾錯、改進、新需求或功能規格變化的適應能力。
(4)可靠性,指程式在給定時間間隔和環境條件下,按設計要求成功執行程式的概率。
(5)程式碼可測試性,指軟體發現故障並隔離、定位故障的能力,以及在一定的時間和成本前提下,進行測試設計、測試執行的能力。
(6)程式碼效能高效,指是儘可能少地佔用系統資源,包括記憶體和執行時間。
(7)可移植性,指為了在原來設計的特定環境之外執行,對系統進行修改的能力。
(8)個人表達方式/個人方便性,指個人程式設計習慣。

原則6.2 要時刻注意易混淆的操作符。
說明:包括易混淆和的易用錯操作符

原則6.3 必須瞭解編譯系統的記憶體分配方式,特別是編譯系統對不同型別的變數的記憶體分配規則,如區域性變數在何處分配、靜態變數在何處分配等。

原則6.4 不僅關注介面,同樣要關注實現。
說明:這個原則看似和“面向介面”程式設計思想相悖,但是實現往往會影響介面,函式所能實現的功能,除了和呼叫者傳遞的引數相關,往往還受制於其他隱含約束,如:實體記憶體的限制,網路狀況,具體看“抽象漏洞原則”。

規則6.1 禁止記憶體操作越界。
說明:記憶體操作主要是指對陣列、指標、記憶體地址等的操作。記憶體操作越界是軟體系統主要錯誤之一,後果往往非常嚴重,所以當我們進行這些操作時一定要仔細小心。
堅持下列措施可以避免記憶體越界:
 陣列的大小要考慮最大情況,避免陣列分配空間不夠。
 避免使用危險函式sprintf /vsprintf/strcpy/strcat/gets操作字串,使用相對安全的函式snprintf/strncpy/strncat/fgets代替。
 使用memcpy/memset時一定要確保長度不要越界
 字串考慮最後的’\0’, 確保所有字串是以’\0’結束
 指標加減操作時,考慮指標型別長度
 陣列下標進行檢查
 使用時sizeof或者strlen計算結構/字串長度,避免手工計算

規則6.2 禁止記憶體洩漏。
說明:記憶體和資源(包括定時器/檔案控制代碼/Socket/佇列/訊號量/GUI等各種資源)洩漏是常見的錯誤。
堅持下列措施可以避免記憶體洩漏:
 異常出口處檢查記憶體、定時器/檔案控制代碼/Socket/佇列/訊號量/GUI等資源是否全部釋放
 刪除結構指標時,必須從底層向上層順序刪除
 使用指標陣列時,確保在釋放陣列時,陣列中的每個元素指標是否已經提前被釋放了
 避免重複分配記憶體
 小心使用有return、break語句的巨集,確保前面資源已經釋放
 檢查佇列中每個成員是否釋放

規則6.3 禁止引用已經釋放的記憶體空間。
說明:在實際程式設計過程中,稍不留心就會出現在一個模組中釋放了某個記憶體塊,而另一模組在隨後的某個時刻又使用了它。要防止這種情況發生。
堅持下列措施可以避免引用已經釋放的記憶體空間:
 記憶體釋放後,把指標置為NULL;使用記憶體指標前進行非空判斷。
 耦合度較強的模組互相呼叫時,一定要仔細考慮其呼叫關係,防止已經刪除的物件被再次使用。
 避免操作已傳送訊息的記憶體。
 自動儲存物件的地址不應賦值給其他的在第一個物件已經停止存在後仍然保持的物件(具有更大作用域的物件或者靜態物件或者從一個函式返回的物件)

規則6.4 程式設計時,要防止差1錯誤。
說明:此類錯誤一般是由於把“<=”誤寫成“<”或“>=”誤寫成“>”等造成的,由此引起的後果,很多情況下是很嚴重的,所以程式設計時,一定要在這些地方小心。當編完程式後,應對這些操作符進行徹底檢查。使用變數時要注意其邊界值的情況。

規則6.5 所有的if … else if結構應該由else子句結束 ;switch語句必須有default分支。

建議6.1 函式中分配的記憶體,在函式退出之前要釋放。
說明:有很多函式申請記憶體,儲存在資料結構中,要在申請處加上註釋,說明在何處釋放。

建議6.2 if語句儘量加上else分支,對沒有else分支的語句要小心對待。

建議6.3 不要濫用goto語句。
說明:goto語句會破壞程式的結構性,所以除非確實需要,最好不使用goto語句。

建議6.4 時刻注意表示式是否會上溢、下溢。

7 程式效率

原則7.1 在保證軟體系統的正確性、簡潔、可維護性、可靠性及可測性的前提下,提高程式碼效率。
說明:不能一味地追求程式碼效率,而對軟體的正確、簡潔、可維護性、可靠性及可測性造成影響。

原則7.2 通過對資料結構、程式演算法的優化來提高效率。

建議7.1 將不變條件的計算移到迴圈體外。
說明:將迴圈中與迴圈無關,不是每次迴圈都要做的操作,移到迴圈外部執行。

建議7.2 對於多維大陣列,避免來回跳躍式訪問陣列成員。

建議7.3 建立資源庫,以減少分配物件的開銷。
說明:例如,使用執行緒池機制,避免執行緒頻繁建立、銷燬的系統呼叫;使用記憶體池,對於頻繁申請、釋放的小塊記憶體,一次性申請一個大塊的記憶體,當系統申請記憶體時,從記憶體池獲取小塊記憶體,使用完畢再釋放到記憶體池中,避免記憶體申請釋放的頻繁系統呼叫.

建議7.4 將多次被呼叫的 “小函式”改為inline函式或者巨集實現。
說明: 如果編譯器支援inline,可以採用inline函式。否則可以採用巨集。

8 註釋

原則8.1 優秀的程式碼可以自我解釋,不通過註釋即可輕易讀懂。
說明:優秀的程式碼不寫註釋也可輕易讀懂,註釋無法把糟糕的程式碼變好,需要很多註釋來解釋的程式碼往往存在壞味道,需要重構。

原則8.2 註釋的內容要清楚、明瞭,含義準確,防止註釋二義性。
說明:有歧義的註釋反而會導致維護者更難看懂程式碼,正如帶兩塊表反而不知道準確時間。

原則8.3 在程式碼的功能、意圖層次上進行註釋,即註釋解釋程式碼難以直接表達的意圖,而不是重複描述程式碼。
說明:註釋的目的是解釋程式碼的目的、功能和採用的方法,提供程式碼以外的資訊,幫助讀者理解程式碼,防止沒必要的重複註釋資訊。

規則8.1 修改程式碼時,維護程式碼周邊的所有註釋,以保證註釋與程式碼的一致性。不再有用的註釋要刪除。
說明:不要將無用的程式碼留在註釋中,隨時可以從原始碼配置庫中找回程式碼;即使只是想暫時排除程式碼,也要留個標註,不然可能會忘記處理它。

規則8.2 檔案頭部應進行註釋,註釋必須列出:版權說明、版本號、生成日期、作者姓名、工號、內容、功能說明、與其它檔案的關係、修改日誌等,標頭檔案的註釋中還應有函式功能簡要說明。
說明:通常標頭檔案要對功能和用法作簡單說明,原始檔包含了更多的實現細節或演算法討論。

規則8.3 函式宣告處註釋描述函式功能、效能及用法,包括輸入和輸出引數、函式返回值、可重入的要求等;定義處詳細描述函式功能和實現要點,如實現的簡要步驟、實現的理由、設計約束等。
說明:重要的、複雜的函式,提供外部使用的介面函式應編寫詳細的註釋。

規則8.4 全域性變數要有較詳細的註釋,包括對其功能、取值範圍以及存取時注意事項等的說明。

規則8.5 註釋應放在其程式碼上方相鄰位置或右方,不可放在下面。如放於上方則需與其上面的程式碼用空行隔開,且與下方程式碼縮排相同。

規則8.6 對於switch語句下的case語句,如果因為特殊情況需要處理完一個case後進入下一個case處理,必須在該case語句處理完、下一個case語句前加上明確的註釋。
說明:這樣比較清楚程式編寫者的意圖,有效防止無故遺漏break語句。

規則8.7 避免在註釋中使用縮寫,除非是業界通用或子系統內標準化的縮寫。

規則8.8 同一產品或專案組統一註釋風格。

建議8.1 避免在一行程式碼或表示式的中間插入註釋。
說明:除非必要,不應在程式碼或表達中間插入註釋,否則容易使程式碼可理解性變差。

建議8.2 註釋應考慮程式易讀及外觀排版的因素,使用的語言若是中、英兼有的,建議多使用中文,除非能用非常流利準確的英文表達。對於有外籍員工的,由產品確定註釋語言。
說明:註釋語言不統一,影響程式易讀性和外觀排版,出於對維護人員的考慮,建議使用中文。

建議8.3 檔案頭、函式頭、全域性常量變數、型別定義的註釋格式採用工具可識別的格式。
說明:採用工具可識別的註釋格式,例如doxygen格式,方便工具匯出註釋形成幫助文件。

9 排版與格式

規則9.1 程式塊採用縮排風格編寫,每級縮排為4個空格。
說明:當前各種編輯器/IDE都支援TAB鍵自動轉空格輸入,需要開啟相關功能並設定相關功能。

規則9.2 相對獨立的程式塊之間、變數說明之後必須加空行。

規則9.3 一條語句不能過長,如不能拆分需要分行寫。一行到底多少字元換行比較合適,產品可以自行確定。
說明:對於目前大多數的PC來說,132比較合適(80/132是VTY常見的行寬值);對於新PC寬屏顯示器較多的產品來說,可以設定更大的值。

規則9.4 多個短語句(包括賦值語句)不允許寫在同一行內,即一行只寫一條語句。

規則9.5 if、for、do、while、case、switch、default等語句獨佔一行。
說明:執行語句必須用縮排風格寫,屬於if、for、do、while、case、switch、default等下一個縮排級別;

規則9.6 在兩個以上的關鍵字、變數、常量進行對等操作時,它們之間的操作符之前、之後或者前後要加空格;進行非對等操作時,如果是關係密切的立即操作符(如->),後不應加空格。
說明:採用這種鬆散方式編寫程式碼的目的是使程式碼更加清晰。

建議9.1 註釋符(包括„/‟„//‟„/‟)與註釋內容之間要用一個空格進行分隔。
說明:這樣可以使註釋的內容部分更清晰。

建議9.2 源程式中關係較為緊密的程式碼應儘可能相鄰。

10 表示式

規則10.1 表示式的值在標準所允許的任何運算次序下都應該是相同的。
說明:除了少數操作符(函式呼叫操作符 ( )、&&、| |、? : 和 , (逗號)) 之外,子表示式所依據的運算次序是未指定的並會隨時更改。注意,運算次序的問題不能使用括號來解決,因為這不是優先順序的問題。

建議10.1 函式呼叫不要作為另一個函式的引數使用,否則對於程式碼的除錯、閱讀都不利。
說明:如下程式碼不合理,僅用於說明當函式作為引數時,由於引數壓棧次數不是程式碼可以控制的,可能造成未知的輸出。

建議10.2 賦值語句不要寫在if等語句中,或者作為函式的引數使用。
說明:因為if語句中,會根據條件依次判斷,如果前一個條件已經可以判定整個條件,則後續條件語句不會再執行,所以可能導致期望的部分賦值沒有得到執行。

建議10.3 用括號明確表示式的操作順序,避免過分依賴預設優先順序。
說明:使用括號強調所使用的操作符,防止因預設的優先順序與設計思想不符而導致程式出錯;同時使得程式碼更為清晰可讀,然而過多的括號會分散程式碼使其降低了可讀性。下面是如何使用括號的建議。

建議10.4 賦值操作符不能使用在產生布爾值的表示式上。
說明:如果布林值表示式需要賦值操作,那麼賦值操作必須在運算元之外分別進行。這可以幫助避免=和= =的混淆,幫助我們靜態地檢查錯誤。

11 程式碼編輯、編譯

規則11.1 使用編譯器的最高告警級別,理解所有的告警,通過修改程式碼而不是降低告警級別來消除所有告警。
說明:編譯器是你的朋友,如果它發出某個告警,這經常說明你的程式碼中存在潛在的問題。

規則11.2 在產品軟體(專案組)中,要統一編譯開關、靜態檢查選項以及相應告警清除策略。
說明:如果必須禁用某個告警,應儘可能單獨區域性禁用,並且編寫一個清晰的註釋,說明為什麼遮蔽。

規則11.3 本地構建工具(如PC-Lint)的配置應該和持續整合的一致。
說明:兩者一致,避免經過本地構建的程式碼在持續整合上構建失敗。

規則11.4 使用版本控制(配置管理)系統,及時簽入通過本地構建的程式碼,確保簽入的程式碼不會影響構建成功。
說明:及時簽入程式碼降低整合難度。

建議11.1 要小心地使用編輯器提供的塊拷貝功能程式設計。

12 可測性

原則12.1 模組劃分清晰,介面明確,耦合性小,有明確輸入和輸出,否則單元測試實施困難。
說明:單元測試實施依賴於:
 模組間的介面定義清楚、完整、穩定;
 模組功能的有明確的驗收條件(包括:預置條件、輸入和預期結果);
 模組內部的關鍵狀態和關鍵資料可以查詢,可以修改;
 模組原子功能的入口唯一;
 模組原子功能的出口唯一;
 依賴集中處理:和模組相關的全域性變數儘量的少,或者採用某種封裝形式。

規則12.1 在同一專案組或產品組內,要有一套統一的為整合測試與系統聯調準備的調測開關及相應列印函式,並且要有詳細的說明。
說明:本規則是針對專案組或產品組的。程式碼至始至終只有一份程式碼,不存在開發版本和測試版本的說法。測試與最終發行的版本是通過編譯開關的不同來實現的。並且編譯開關要規範統一。統一使用編譯開關來實現測試版本與發行版本的區別,一般不允許再定義其它新的編譯開關。

規則12.2 在同一專案組或產品組內,調測列印的日誌要有統一的規定。
說明:統一的調測日誌記錄便於整合測試,具體包括:
 統一的日誌分類以及日誌級別;
 通過命令列、網管等方式可以配置和改變日誌輸出的內容和格式;
 在關鍵分支要記錄日誌,日誌建議不要記錄在原子函式中,否則難以定位;
 除錯日誌記錄的內容需要包括檔名/模組名、程式碼行號、函式名、被呼叫函式名、錯誤碼、錯誤發生的環境等。

規則12.3 使用斷言記錄內部假設。
說明:斷言是對某種內部模組的假設條件進行檢查,如果假設不成立,說明存在程式設計、設計錯誤。斷言可以對在系統中隱藏很深,用其它手段極難發現的問題進行定位,從而縮短軟體問題定位時間,提高系統的可測性。

規則12.4 不能用斷言來檢查執行時錯誤。
說明:斷言是用來處理內部程式設計或設計是否符合假設;不能處理對於可能會發生的且必須處理的情況要寫防錯程式,而不是斷言。如某模組收到其它模組或鏈路上的訊息後,要對訊息的合理性進行檢查,此過程為正常的錯誤檢查,不能用斷言來實現。

13 安全性

原則13.1 對使用者輸入進行檢查。
說明:不能假定使用者輸入都是合法的,因為難以保證不存在惡意使用者,即使是合法使用者也可能由於誤用誤操作而產生非法輸入。使用者輸入通常需要經過檢驗以保證安全。

規則13.1 確保所有字串是以NULL結束。
說明:C語言中‟\0‟作為字串的結束符,即NULL結束符。標準字串處理函式(如strcpy()、strlen())依賴NULL結束符來確定字串的長度。沒有正確使用NULL結束字串會導致緩衝區溢位和其它未定義的行為。

規則13.2 不要將邊界不明確的字串寫到固定長度的陣列中。
說明:邊界不明確的字串(如來自gets()、getenv()、scanf()的字串),長度可能大於目標陣列長度,直接拷貝到固定長度的陣列中容易導致緩衝區溢位。

規則13.3 避免整數溢位。
說明:當一個整數被增加超過其最大值時會發生整數上溢,被減小小於其最小值時會發生整數下溢。帶符號和無符號的數都有可能發生溢位。

規則13.4 避免符號錯誤。
說明:有時從帶符號整型轉換到無符號整型會發生符號錯誤,符號錯誤並不丟失資料,但資料失去了原來的含義

規則13.5:避免截斷錯誤。
說明:將一個較大整型轉換為較小整型,並且該數的原值超出較小型別的表示範圍,就會發生截斷錯誤,原值的低位被保留而高位被丟棄。截斷錯誤會引起資料丟失。

規則13.6:確保格式字元和引數匹配。
說明:使用格式化字串應該小心,確保格式字元和引數之間的匹配,保留數量和資料型別。格式字元和引數之間的不匹配會導致未定義的行為。大多數情況下,不正確的格式化字串會導致程式異常終止。

規則13.7 避免將使用者輸入作為格式化字串的一部分或者全部。
說明:呼叫格式化I/O函式時,不要直接或者間接將使用者輸入作為格式化字串的一部分或者全部。攻擊者對一個格式化字串擁有部分或完全控制,存在以下風險:程序崩潰、檢視棧的內容、改寫記憶體、甚至執行任意程式碼。

規則13.8 避免使用strlen()計算二進位制資料的長度。
說明:strlen()函式用於計算字串的長度,它返回字串中第一個NULL結束符之前的字元的數量。因此用strlen()處理檔案I/O函式讀取的內容時要小心,因為這些內容可能是二進位制也可能是文字。

規則13.9 使用int型別變數來接受字元I/O函式的返回值。
說明:字元I/O函式fgetc()、getc()和getchar()都從一個流讀取一個字元,並把它以int值的形式返回。如果這個流到達了檔案尾或者發生讀取錯誤,函式返回EOF。fputc()、putc()、putchar()和ungetc()也返回一個字元或EOF。如果這些I/O函式的返回值需要與EOF進行比較,不要將返回值轉換為char型別。因為char是有符號8位的值,int是32位的值。如果getchar()返回的字元的ASCII值為0xFF,轉換為char型別後將被解釋為EOF。因為這個值被有符號擴充套件為0xFFFFFFFF(EOF的值)執行比較。

規則13.10 防止命令注入。
說明:C99函式system()通過呼叫一個系統定義的命令解析器(如UNIX的shell,Windows的CMD.exe)來執行一個指定的程式/命令。類似的還有POSIX的函式popen()。如果system()的引數由使用者的輸入組成,惡意使用者可以通過構造惡意輸入,改變system()呼叫的行為。

14 單元測試

規則14.1 在編寫程式碼的同時,或者編寫程式碼前,編寫單元測試用例驗證軟體設計/編碼的正確。

建議14.1 單元測試關注單元的行為而不是實現,避免針對函式的測試。
說明:應該將被測單元看做一個被測的整體,根據實際資源、進度和質量風險,權衡程式碼覆蓋、打樁工作量、補充測試用例的難度、被測物件的穩定程度等,一般情況下建議關注模組/元件的測試,儘量避免針對函式的測試。儘管有時候單個用例只能專注於對某個具體函式的測試,但我們關注的應該是函式的行為而不是其具體實現細節。

15 可移植性

規則15.1 不能定義、重定義或取消定義標準庫/平臺中保留的識別符號、巨集和函式。

建議15.1 不使用與硬體或作業系統關係很大的語句,而使用建議的標準語句,以提高軟體的可移植性和可重用性。
說明:使用標準的資料型別,有利於程式的移植。

推薦閱讀材料:
C語言介面與實現
敏捷軟體開發
函式式語言的優勢
抽象漏洞原則
程式碼大全
doxygen中文手冊
程式碼整潔之道
google c++程式設計規範
汽車業c語言使用規範(misra)

2018.1.27