DKBA
華爲技術有限公司內部技術規範
DKBA 2826-2011.5
C語言編程規範
2011年5月9日發佈 2011年5月9日實施
華爲技術有限公司
Huawei Technologies Co., Ltd.
版權全部 侵權必究
All rights reserved
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第2頁,共61頁Page 2 , Total61
修訂聲明Revision declaration
本規範擬製與解釋部門:
本規範的相關係列規範或文件:
相關國際規範或文件一致性:
替代或做廢的其它規範或文件:
相關規範或文件的相互關係:
規範號
主要起草部門專家
主要評審部門專家
修訂狀況
DKBAxxxx.x-xxxx.xx
PSST質量部:
郭曙光00121837
網絡:
張偉00118807
周燦00056781
王晶00041937
陳藝彪00036913
IP開發部:
薛治00038309
核心網:
張小林00058208
王德喜00040674
李明勝00042021
軟件公司:
文 滔00119601
無線:
劉愛華00162172
中研:
譚洪00162654
PSST質量部:
李重霄00117374
郭永生00120218
核心網:
張進柏00120359
中研:
張建保00116237
無線:
蘇光牛00118740
鄭銘00118617
陶永祥00120482
軟件公司:
周代兵00120359
劉心紅00118478
朱文琦00172539
網絡:
王玎00168059
黃維東49827
IP開發部:
饒遠00152313
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第3頁,共61頁Page 3 , Total61
目 錄Table of Contents
0 規範制訂說明 ................................................................................................................................ 5
0.1 前言 ...................................................................................................................................... 5
0.2 代碼整體原則 ....................................................................................................................... 5
0.3 規範實施、解釋 .................................................................................................................... 6
0.4 術語定義 ............................................................................................................................... 6
1 頭文件 ........................................................................................................................................... 6
2 函數 ............................................................................................................................................. 12
3 標識符命名與定義 ....................................................................................................................... 21
3.1 通用命名規則 ..................................................................................................................... 21
3.2 文件命名規則 ..................................................................................................................... 23
3.3 變量命名規則 ..................................................................................................................... 23
3.4 函數命名規則 ..................................................................................................................... 24
3.5 宏的命名規則 ..................................................................................................................... 24
4 變量 ............................................................................................................................................. 25
5 宏、常量...................................................................................................................................... 28
6 質量保證...................................................................................................................................... 32
7 程序效率...................................................................................................................................... 36
8 註釋 ............................................................................................................................................. 39
9 排版與格式 .................................................................................................................................. 44
10 表達式 ..................................................................................................................................... 46
11 代碼編輯、編譯 ...................................................................................................................... 49
12 可測性 ..................................................................................................................................... 50
13 安全性 ..................................................................................................................................... 51
13.1 字符串操做安全 .................................................................................................................. 51
13.2 整數安全 ............................................................................................................................. 52
13.3 格式化輸出安全 .................................................................................................................. 56
13.4 文件I/O安全 ........................................................................................................................ 57
13.5 其它 .................................................................................................................................... 59
14 單元測試 ................................................................................................................................. 59
15 可移植性 ................................................................................................................................. 60
16 業界編程規範 .......................................................................................................................... 60
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第4頁,共61頁Page 4 , Total61
C語言編程規範
範 圍:
本規範適用於公司內使用C語言編碼的全部軟件。本規範自發布之日起生效,之後新編寫的和修改的代碼應遵照本規範。
簡 介:
本規範制定了編寫C語言程序的基本原則、規則和建議。從代碼的清晰、簡潔、可測試、安全、程序效率、可移植各個方面對C語言編程做出了具體指導。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第5頁,共61頁Page 5 , Total61
0 規範制訂說明
0.1 前言
爲提升產品代碼質量,指導廣大軟件開發人員編寫出簡潔、可維護、可靠、可測試、高效、可移植的代碼,編程規範修訂工做組分析、總結了我司的各類典型編碼問題,並參考了業界編程規範近年來的成果,從新對我司1999年版編程規範進行了梳理、優化、刷新,編寫了本規範。
本規範將分爲完整版和精簡版,完整版將包括更多的樣例、規範的解釋以及參考材料(what & why),而精簡版將只包含規則部分(what)以便查閱。
在本規範的最後,列出了一些業界比較優秀的編程規範,做爲延伸閱讀參考材料。
0.2 代碼整體原則
一、清晰第一
清晰性是易於維護、易於重構的程序必需具有的特徵。代碼首先是給人讀的,好的代碼應當能夠像文章同樣發聲朗誦出來。
目前軟件維護期成本佔整個生命週期成本的40%~90%。根據業界經驗,維護期變動代碼的成本,小型系統是開發期的5倍,大型系統(100萬行代碼以上)能夠達到100倍。業界的調查指出,開發組平均大約一半的人力用於彌補過去的錯誤,而不是添加新的功能來幫助公司提升競爭力。
「程序必須爲閱讀它的人而編寫,只是順便用於機器執行。」——Harold Abelson 和 Gerald Jay Sussman
「編寫程序應該以人爲本,計算機第二。」——Steve McConnell
本規範經過後文中的原則(如頭優秀的代碼能夠自我解釋,不經過註釋便可輕易讀懂/頭文件中適合放置接口的聲明,不適合放置實現/除了常見的通用縮寫之外,不使用單詞縮寫,不得使用漢語拼音)、規則(如防止局部變量與全局變量同名)等說明清晰的重要性。
通常狀況下,代碼的可閱讀性高於性能,只有肯定性能是瓶頸時,才應該主動優化。
二、簡潔爲美
簡潔就是易於理解而且易於實現。代碼越長越難以看懂,也就越容易在修改時引入錯誤。寫的代碼越多,意味着出錯的地方越多,也就意味着代碼的可靠性越低。所以,咱們提倡你們經過編寫簡潔明瞭的代碼來提高代碼可靠性。
廢棄的代碼(沒有被調用的函數和全局變量)要及時清除,重複代碼應該儘量提煉成函數。
本規範經過後文中的原則(如文件應當職責單一/一個函數僅完成一件功能)、規則(重複代碼應該儘量提煉成函數/避免函數過長,新增函數不超過50行)等說明簡潔的重要性。
三、選擇合適的風格,與代碼原有風格保持一致
產品全部人共同分享同一種風格所帶來的好處,遠遠超出爲了統一而付出的代價。在公司已有編碼規範的指導下,審慎地編排代碼以使代碼儘量清晰,是一項很是重要的技能。若是重構/修改其餘風格的代碼時,比較明智的作法是根據現有代碼的現有風格繼續編寫代碼,或者使用格式轉換工具進行轉
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第6頁,共61頁Page 6 , Total61
換成公司內部風格。
0.3 規範實施、解釋
本規範制定了編寫C語言程序的基本原則、規則和建議。
本規範適用於公司內使用C語言編碼的全部軟件。本規範自發布之日起生效,對之後新編寫的和修改的代碼應遵照本規範。
本規範由質量體系發佈和維護。實施中遇到問題,能夠到論壇http://hi3ms.huawei.com/group/1735/threads.html上討論。
在某些狀況下(如BSP軟件)須要違反本文檔給出的規則時,相關團隊必須經過一個正式的流程來評審、決策規則違反的部分,個體程序員不得違反本規範中的相關規則。
0.4 術語定義
原則:編程時必須堅持的指導思想。
規則:編程時強制必須遵照的約定。
建議:編程時必須加以考慮的約定。
說明:對此原則/規則/建議進行必要的解釋。
示例:對此原則/規則/建議從正、反兩個方面給出例子。
延伸閱讀材料:建議進一步閱讀的參考材料。
1 頭文件
背景
對於C語言來講,頭文件的設計體現了大部分的系統設計。不合理的頭文件佈局是編譯時間過長的根因,不合理的頭文件實際上不合理的設計。
術語定義:
依賴:本章節特指編譯依賴。若x.h包含了y.h,則稱做x依賴y。依賴關係會進行傳導,如x.h包含y.h,而y.h又包含了z.h,則x經過y依賴了z。依賴將致使編譯時間的上升。雖然依賴是不可避免的,也是必須的,可是不良的設計會致使整個系統的依賴關係無比複雜,使得任意一個文件的修改都要從新編譯整個系統,致使編譯時間巨幅上升。
在一個設計良好的系統中,修改一個文件,只須要從新編譯數個,甚至是一個文件。
某產品曾經作過一個實驗,把全部函數的實現經過工具註釋掉,其編譯時間只減小了不到10%,究其緣由,在於A包含B,B包含C,C包含D,最終幾乎每個源文件都包含了項目組全部的頭文件,從而致使絕大部分編譯時間都花在解析頭文件上。
某產品更有一個「優秀實踐」,用於將.c文件經過工具合併成一個比較大的.c文件,從而大幅度提升編譯效率。其根本緣由仍是在於經過合併.c文件減小了頭文件解析次數。可是,這樣的「優秀實踐」是對合理劃分.c文件的一種破壞。
大部分產品修改一處代碼,都得須要編譯整個工程,對於TDD之類的實踐,要求對於模塊級別的編譯時間控制在秒級,即便使用分佈式編譯也難以實現,最終仍然須要合理的劃分頭文件、以及頭文件之間的包含關係,從根本上下降編譯時間。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第7頁,共61頁Page 7 , Total61
《google C++ Style Guide》1.2 頭文件依賴 章節也給出了相似的闡述:
若包含了頭文件aa.h,則就引入了新的依賴:一旦aa.h被修改,任何直接和間接包含aa.h代碼都會被從新編譯。若是aa.h又包含了其餘頭文件如bb.h,那麼bb.h的任何改變都將致使全部包含了aa.h的代碼被從新編譯,在敏捷開發方式下,代碼會被頻繁構建,漫長的編譯時間將極大的阻礙頻繁構建。所以,咱們傾向於減小包含頭文件,尤爲是在頭文件中包含頭文件,以控制改動代碼後的編譯時間。
合理的頭文件劃分體現了系統設計的思想,可是從編程規範的角度看,仍然有一些通用的方法,用來合理規劃頭文件。本章節介紹的一些方法,對於合理規劃頭文件會有必定的幫助。
原則1.1 頭文件中適合放置接口的聲明,不適合放置實現。
說明:頭文件是模塊(Module)或單元(Unit)的對外接口。頭文件中應放置對外部的聲明,如對外提供的函數聲明、宏定義、類型定義等。
內部使用的函數(至關於類的私有方法)聲明不該放在頭文件中。
內部使用的宏、枚舉、結構定義不該放入頭文件中。
變量定義不該放在頭文件中,應放在.c文件中。
變量的聲明儘可能不要放在頭文件中,亦即儘可能不要使用全局變量做爲接口。變量是模塊或單元的內部實現細節,不該經過在頭文件中聲明的方式直接暴露給外部,應經過函數接口的方式進行對外暴露。 即便必須使用全局變量,也只應當在.c中定義全局變量,在.h中僅聲明變量爲全局的。
延伸閱讀材料:《C語言接口與實現》(David R. Hanson 著 傅蓉 周鵬 張昆琪 權威 譯 機械工業出版社 2004年1月)(英文版: "C Interfaces and Implementations")
原則1.2 頭文件應當職責單一。
說明:頭文件過於複雜,依賴過於複雜是致使編譯時間過長的主要緣由。不少現有代碼中頭文件過大,職責過多,再加上循環依賴的問題,可能致使爲了在.c中使用一個宏,而包含十幾個頭文件。
示例:以下是某平臺定義WORD類型的頭文件:
#include <VXWORKS.H>
#include <KERNELLIB.H>
#include <SEMLIB.H>
#include <INTLIB.H>
#include <TASKLIB.H>
#include <MSGQLIB.H>
#include <STDARG.H>
#include <FIOLIB.H>
#include <STDIO.H>
#include <STDLIB.H>
#include <CTYPE.H>
#include <STRING.H>
#include <ERRNOLIB.H>
#include <TIMERS.H>
#include <MEMLIB.H>
#include <TIME.H>
#include <WDLIB.H>
#include <SYSLIB.H>
#include <TASKHOOKLIB.H>
#include <REBOOTLIB.H>
…
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第8頁,共61頁Page 8 , Total61
typedef unsigned short WORD;
…
這個頭文件不但定義了基本數據類型WORD,還包含了stdio.h syslib.h等等不經常使用的頭文件。若是工程中有10000個源文件,而其中100個源文件使用了stdio.h的printf,因爲上述頭文件的職責過於龐大,而WORD又是每個文件必須包含的,從而致使stdio.h/syslib.h等可能被沒必要要的展開了9900次,大大增長了工程的編譯時間。
原則1.3 頭文件應向穩定的方向包含。
說明:頭文件的包含關係是一種依賴,通常來講,應當讓不穩定的模塊依賴穩定的模塊,從而當不穩定的模塊發生變化時,不會影響(編譯)穩定的模塊。
就咱們的產品來講,依賴的方向應該是:產品依賴於平臺,平臺依賴於標準庫。某產品線平臺的代碼中已經包含了產品的頭文件,致使平臺沒法單獨編譯、發佈和測試,是一個很是糟糕的反例。
除了不穩定的模塊依賴於穩定的模塊外,更好的方式是兩個模塊共同依賴於接口,這樣任何一個模塊的內部實現更改都不須要從新編譯另一個模塊。在這裏,咱們假設接口自己是最穩定的。
延伸閱讀材料:編者推薦開發人員使用「依賴倒置」原則,即由使用者制定接口,服務提供者實現接口,更具體的描述能夠參見《敏捷軟件開發:原則、模式與實踐》(Robert C.Martin 著 鄧輝 譯 清華大學出版社2003年9月) 的第二部分「敏捷設計」章節。
規則1.1 每個.c文件應有一個同名.h文件,用於聲明須要對外公開的接口。
說明:若是一個.c文件不須要對外公佈任何接口,則其就不該當存在,除非它是程序的入口,如main函數所在的文件。
現有某些產品中,習慣一個.c文件對應兩個頭文件,一個用於存放對外公開的接口,一個用於存放內部須要用到的定義、聲明等,以控制.c文件的代碼行數。編者不提倡這種風格。這種風格的根源在於源文件過大,應首先考慮拆分.c文件,使之不至於太大。另外,一旦把私有定義、聲明放到獨立的頭文件中,就沒法從技術上避免別人include之,難以保證這些定義最後真的只是私有的。
本規則反過來並不必定成立。有些特別簡單的頭文件,如命令ID定義頭文件,不須要有對應的.c存在。
示例:對於以下場景,如在一個.c中存在函數調用關係:
void foo()
{
bar();
}
void bar()
{
Do something;
}
必須在foo以前聲明bar,不然會致使編譯錯誤。
這一類的函數聲明,應當在.c的頭部聲明,並聲明爲static的,以下:
static void bar();
void foo()
{
bar();
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第9頁,共61頁Page 9 , Total61
}
void bar()
{
Do something;
}
規則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 頭文件應當自包含。
說明:簡單的說,自包含就是任意一個頭文件都可獨立編譯。若是一個文件包含某個頭文件,還要包含另一個頭文件才能工做的話,就會增長交流障礙,給這個頭文件的用戶增添沒必要要的負擔。
示例:
若是a.h不是自包含的,須要包含b.h才能編譯,會帶來的危害:
每一個使用a.h頭文件的.c文件,爲了讓引入的a.h的內容編譯經過,都要包含額外的頭文件b.h。
額外的頭文件b.h必須在a.h以前進行包含,這在包含順序上產生了依賴。
注意:該規則須要與「.c/.h文件禁止包含用不到的頭文件」規則一塊兒使用,不能爲了讓a.h自包含,而在a.h中包含沒必要要的頭文件。a.h要剛剛能夠自包含,不能在a.h中多包含任何知足自包含以外的其餘頭文件。
規則1.5 老是編寫內部#include保護符(#define 保護)。
說明:屢次包含一個頭文件能夠經過認真的設計來避免。若是不能作到這一點,就須要採起阻止頭文件內容被包含多於一次的機制。
一般的手段是爲每一個文件配置一個宏,當頭文件第一次被包含時就定義這個宏,並在頭文件被再次包含時使用它以排除文件內容。
全部頭文件都應當使用#define 防止頭文件被多重包含,命名格式爲FILENAME_H,爲了保證惟一性,更好的命名是PROJECTNAME_PATH_FILENAME_H。
注:沒有在宏最前面加上「_",即便用FILENAME_H代替_FILENAME_H_,是由於通常以"_"和」__"開頭的標識符爲系統保留或者標準庫使用,在有些靜態檢查工具中,若全局可見的標識符以"_"開頭會給出告警。
定義包含保護符時,應該遵照以下規則:
1)保護符使用惟一名稱;
2)不要在受保護部分的先後放置代碼或者註釋。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第10頁,共61頁Page 10 , Total61
示例:假定VOS工程的timer模塊的timer.h,其目錄爲VOS/include/timer/timer.h,應按以下方式保護:
#ifndef VOS_INCLUDE_TIMER_TIMER_H
#define VOS_INCLUDE_TIMER_TIMER_H
...
#endif
也能夠使用以下簡單方式保護:
#ifndef TIMER_H
#define TIMER_H
..
#endif
例外狀況:頭文件的版權聲明部分以及頭文件的總體註釋部分(如闡述此頭文件的開發背景、使用注意事項等)能夠放在保護符(#ifndef XX_H)前面。
規則1.6 禁止在頭文件中定義變量。
說明:在頭文件中定義變量,將會因爲頭文件被其餘.c文件包含而致使變量重複定義。
規則1.7 只能經過包含頭文件的方式使用其餘.c提供的接口,禁止在.c中經過extern的方式使用外部函數接口、變量。
說明:若a.c使用了b.c定義的foo()函數,則應當在b.h中聲明extern int foo(int input);並在a.c中經過#include <b.h>來使用foo。禁止經過在a.c中直接寫extern int foo(int input);來使用foo,後面這種寫法容易在foo改變時可能致使聲明和定義不一致。
規則1.8 禁止在extern "C"中包含頭文件。
說明:在extern "C"中包含頭文件,會致使extern "C"嵌套,Visual Studio對extern "C"嵌套層次有限制,嵌套層次太多會編譯錯誤。
在extern "C"中包含頭文件,可能會致使被包含頭文件的原有意圖遭到破壞。例如,存在a.h和b.h兩個頭文件:
#ifndef A_H__
#define A_H__
#ifdef __cplusplus
void foo(int);
#define a(value) foo(value)
#else
void a(int)
#endif
#endif /* A_H__ */
#ifndef B_H__
#define B_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "a.h"
void b();
#ifdef __cplusplus
}
#endif
#endif /* B_H__ */
使用C++預處理器展開b.h,將會獲得
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第11頁,共61頁Page 11 , Total61
extern "C" {
void foo(int);
void b();
}
按照a.h做者的本意,函數foo是一個C++自由函數,其連接規範爲"C++"。但在b.h中,因爲#include "a.h"被放到了extern "C" { }的內部,函數foo的連接規範被不正確地更改了。
示例:錯誤的使用方式:
extern 「C」
{
#include 「xxx.h」
...
}
正確的使用方式:
#include 「xxx.h」
extern 「C」
{
...
}
建議1.1 一個模塊一般包含多個.c文件,建議放在同一個目錄下,目錄名即爲模塊名。爲方便外部使用者,建議每個模塊提供一個.h,文件名爲目錄名。
說明:須要注意的是,這個.h並非簡單的包含全部內部的.h,它是爲了模塊使用者的方便,對外總體提供的模塊接口。
以Google test(簡稱GTest)爲例,GTest做爲一個總體對外提供C++單元測試框架,其1.5版本的gtest工程下有6個源文件和12個頭文件。可是它對外只提供一個gtest.h,只要包含gtest.h便可使用GTest提供的全部對外提供的功能,使用者沒必要關係GTest內部各個文件的關係,即便之後GTest的內部實現改變了,好比把一個源文件c拆成兩個源文件,使用者也沒必要關心,甚至若是對外功能不變,連從新編譯都不須要。
對於有些模塊,其內部功能相對鬆散,可能並不必定須要提供這個.h,而是直接提供各個子模塊或者.c的頭文件。
好比產品廣泛使用的VOS,做爲一個大模塊,其內部有不少子模塊,他們之間的關係相對比較鬆散,就不適合提供一個vos.h。而VOS的子模塊,如Memory(僅做舉例說明,與實際狀況可能有所出入),其內部實現高度內聚,雖然其內部實現可能有多個.c和.h,可是對外只須要提供一個Memory.h聲明接口。
建議1.2 若是一個模塊包含多個子模塊,則建議每個子模塊提供一個對外的.h,文件名爲子模塊名。
說明:下降接口使用者的編寫難度。
建議1.3 頭文件不要使用非習慣用法的擴展名,如.inc。
說明:目前不少產品中使用了.inc做爲頭文件擴展名,這不符合c語言的習慣用法。在使用.inc做爲頭文件擴展名的產品,習慣上用於標識此頭文件爲私有頭文件。可是從產品的實際代碼來看,這一條並無被遵照,一個.inc文件被多個.c包含比比皆是。本規範不提倡將私有定義單獨放在頭文件中,具
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第12頁,共61頁Page 12 , Total61
體見 規則1.1。
除此以外,使用.inc還致使source insight、Visual stduio等IDE工具沒法識別其爲頭文件,致使不少功能不可用,如「跳轉到變量定義處」。雖然能夠經過配置,強迫IDE識別.inc爲頭文件,可是有些軟件沒法配置,如Visual Assist只能識別.h而沒法經過配置識別.inc。
建議1.4 同一產品統一包含頭文件排列方式。
說明:常見的包含頭文件排列方式:功能塊排序、文件名升序、穩定度排序。
示例1:
以升序方式排列頭文件能夠避免頭文件被重複包含,如:
#include <a.h>
#include <b.h>
#include <c/d.h>
#include <c/e.h>
#include <f.h>
示例2:
以穩定度排序,建議將不穩定的頭文件放在前面,如把產品的頭文件放在平臺的頭文件前面,以下:
#include <product.h>
#include <platform.h>
相對來講,product.h修改的較爲頻繁,若是有錯誤,沒必要編譯platform.h就能夠發現product.h的錯誤,能夠部分減小編譯時間。
2 函數
背景
函數設計的精髓:編寫整潔函數,同時把代碼有效組織起來。
整潔函數要求:代碼簡單直接、不隱藏設計者的意圖、用乾淨利落的抽象和直截了當的控制語句將函數有機組織起來。
代碼的有效組織包括:邏輯層組織和物理層組織兩個方面。邏輯層,主要是把不一樣功能的函數經過某種聯繫組織起來,主要關注模塊間的接口,也就是模塊的架構。物理層,不管使用什麼樣的目錄或者名字空間等,須要把函數用一種標準的方法組織起來。例如:設計良好的目錄結構、函數名字、文件組織等,這樣能夠方便查找。
原則2.1 一個函數僅完成一件功能。
說明:一個函數實現多個功能給開發、使用、維護都帶來很大的困難。
將沒有關聯或者關聯很弱的語句放到同一函數中,會致使函數職責不明確,難以理解,難以測試和改動。
案例:realloc。在標準C語言中,realloc是一個典型的不良設計。這個函數基本功能是從新分配內存,但它承擔了太多的其餘任務:若是傳入的指針參數爲NULL就分配內存,若是傳入的大小參數爲0就釋放內存,若是可行則就地從新分配,若是不行則移到其餘地方分配。若是沒有足夠可用的內存用來完成從新分配(擴大原來的內存塊或者分配新的內存塊),則返回NULL,而原來的內存塊保持不變。這個函數不易擴展,容易致使問題。例以下面代碼容易致使內存泄漏:
char *buffer = (char *)malloc(XXX_SIZE);
.....
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第13頁,共61頁Page 13 , Total61
buffer = (char *)realloc(buffer, NEW_SIZE);
若是沒有足夠可用的內存用來完成從新分配,函數返回爲NULL,致使buffer原來指向的內存被丟失。
延伸閱讀材料:《敏捷軟件開發:原則、模式與實踐》 第八章,單一職責原則(SRP)
原則2.2 重複代碼應該儘量提煉成函數。
說明:重複代碼提煉成函數能夠帶來維護成本的下降。
重複代碼是我司不良代碼最典型的特徵之一。在「代碼能用就不改」的指導原則之下,大量的煙囪式設計及其實現充斥着各產品代碼之中。新需求增長帶來的代碼拷貝和修改,隨着時間的遷移,產品中堆砌着許多相似或者重複的代碼。
項目組應當使用代碼重複度檢查工具,在持續集成環境中持續檢查代碼重複度指標變化趨勢,並對新增重複代碼及時重構。當一段代碼重複兩次時,即應考慮消除重複,當代碼重複超過三次時,應當馬上着手消除重複。
通常狀況下,能夠經過提煉函數的形式消除重複代碼。
示例:
UC ccb_aoc_process( )
{
... ...
struct AOC_E1_E7 aoc_e1_e7;
aoc_e1_e7.aoc = 0;
aoc_e1_e7.e[0] = 0;
... ... //aoc_e1_e7.e[i]從到賦值,下同
aoc_e1_e7.e[6] = 0;
aoc_e1_e7.tariff_rate = 0;
... ...
if (xxx)
{
if (xxx)
{
aoc_e1_e7.e[0] = 0;
... ...
aoc_e1_e7.e[6] = 0;
aoc_e1_e7.tariff_rate = 0;
}
... ...
}
else if (xxx)
{
if (xxx)
{
aoc_e1_e7.e[0] = 0;
... ...
aoc_e1_e7.e[6] = 0;
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第14頁,共61頁Page 14 , Total61
aoc_e1_e7.tariff_rate = 0;
}
ccb_caller_e1 = aoc_e1_e7.e[0];
... ...
ccb_caller_e7 = aoc_e1_e7.e[6];
ccb_caller_tariff_rate = aoc_e1_e7.tariff_rate;
... ...
}
... ...
if (xxx)
{
if (xxx)
{
if (xxx)
{
aoc_e1_e7.e[0] = 0;
... ...
aoc_e1_e7.e[6] = 0;
aoc_e1_e7.tariff_rate = 0;
}
... ...
}
else if (xxx)
{
if (xxx)
{
aoc_e1_e7.e[0] = 0;
... ...
aoc_e1_e7.e[6] = 0;
aoc_e1_e7.tariff_rate = 0;
}
ccb_caller_e1 = aoc_e1_e7.e[0];
... ...
ccb_caller_e7 = aoc_e1_e7.e[6];
ccb_caller_tariff_rate = aoc_e1_e7.tariff_rate;
... ...
}
return 1;
}
else
{
return 0;
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第15頁,共61頁Page 15 , Total61
}
}
刺鼻的代碼壞味充斥着這個函數。紅色字體的部分是簡單的代碼重複,粗體字部分是代碼結構的重複,將重複部分提煉成一個函數便可消除重複。
規則2.1 避免函數過長,新增函數不超過50行(非空非註釋行)。
說明:本規則僅對新增函數作要求,對已有函數修改時,建議不增長代碼行。
過長的函數每每意味着函數功能不單一,過於複雜(參見原則2.1:一個函數只完成一個功能)。
函數的有效代碼行數,即NBNC(非空非註釋行)應當在[1,50]區間。
例外:某些實現算法的函數,因爲算法的聚合性與功能的全面性,可能會超過50行。
延伸閱讀材料:業界廣泛認爲一個函數的代碼行不要超過一個屏幕,避免來回翻頁影響閱讀;通常的代碼度量工具建議都對此進行檢查,例如Logiscope的函數度量:"Number of Statement" (函數中的可執行語句數)建議不超過20行,QA C建議一個函數中的全部行數(包括註釋和空白行)不超過50行。
規則2.2 避免函數的代碼塊嵌套過深,新增函數的代碼塊嵌套不超過4層。
說明:本規則僅對新增函數作要求,對已有的代碼建議不增長嵌套層次。
函數的代碼塊嵌套深度指的是函數中的代碼控制塊(例如:if、for、while、switch等)之間互相包含的深度。每級嵌套都會增長閱讀代碼時的腦力消耗,由於須要在腦子裏維護一個「棧」(好比,進入條件語句、進入循環„„)。應該作進一步的功能分解,從而避免使代碼的閱讀者一次記住太多的上下文。優秀代碼參考值:[1, 4]。
示例:以下代碼嵌套深度爲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;
}
}
}
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第16頁,共61頁Page 16 , Total61
規則2.3 可重入函數應避免使用共享變量;若須要使用,則應經過互斥手段(關中斷、信號量)對其加以保護。
說明:可重入函數是指可能被多個任務併發調用的函數。在多任務操做系統中,函數具備可重入性是多個任務能夠共用此函數的必要條件。共享變量指的全局變量和static變量。
編寫C語言的可重入函數時,不該使用static局部變量,不然必須通過特殊處理,才能使函數具備可重入性。
示例:函數square_exam返回g_exam平方值。那麼以下函數不具備可重入性。
int g_exam;
unsigned int example( int para )
{
unsigned int temp;
g_exam = para; // (**)
temp = square_exam ( );
return temp;
}
此函數若被多個線程調用的話,其結果多是未知的,由於當(**)語句剛執行完後,另一個使用本函數的線程可能正好被激活,那麼當新激活的線程執行到此函數時,將使g_exam賦於另外一個不一樣的para值,因此當控制從新回到「temp =square_exam ( )」後,計算出的temp極可能不是預想中的結果。此函數應以下改進。
int g_exam;
unsigned int example( int para )
{
unsigned int temp;
[申請信號量操做] // 若申請不到「信號量」,說明另外的進程正處於
g_exam = para; //給g_exam賦值並計算其平方過程當中(即正在使用此
temp = square_exam( ); // 信號),本進程必須等待其釋放信號後,纔可繼
[釋放信號量操做] // 續執行。其它線程必須等待本線程釋放信號量後
// 才能再使用本信號。
return temp;
}
規則2.4 對參數的合法性檢查,由調用者負責仍是由接口函數負責,應在項目組/模塊內應統一規定。缺省由調用者負責。
說明:對於模塊間接口函數的參數的合法性檢查這一問題,每每有兩個極端現象,即:要麼是調用者和被調用者對參數均不做合法性檢查,結果就遺漏了合法性檢查這一必要的處理過程,形成問題隱患;要麼就是調用者和被調用者均對參數進行合法性檢查,這種狀況雖不會形成問題,但產生了冗餘代碼,下降了效率。
示例:下面紅色部分的代碼在每個函數中都寫了一次,致使代碼有較多的冗餘。若是函數的參數比較多,並且判斷的條件比較複雜(好比:一個整形數字須要判斷範圍等),那麼冗餘的代碼會大面積充斥着業務代碼。
void PidMsgProc(MsgBlock *Msg)
{
MsgProcItem *func = NULL;
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第17頁,共61頁Page 17 , Total61
if (Msg == NULL)
{
return;
}
... ...
GetMsgProcFun(Msg, &func);
func(Msg);
return;
}
int GetMsgProcFun(MsgBlock *Msg, MsgProcItem **func)
{
if (Msg == NULL)
{
return 1;
}
... ...
*func = VOS_NULL_PTR;
for (Index = 0; Index < NELEM(g_MsgProcTable); Index++)
{
if ((g_MsgProcTable[Index].FlowType == Msg->FlowType)
&& (g_MsgProcTable[Index].Status == Msg->Status)
&& (g_MsgProcTable[Index].MsgType == Msg->MsgType))
{
*func = &(g_MsgProcTable[Index]);
return 0;
}
}
return 1;
}
int ServiceProcess(int CbNo, MsgBlock *Msg)
{
if ( Msg == NULL)
{
return 1;
}
... ...
// 業務處理代碼
... ...
return 0;
}
規則2.5 對函數的錯誤返回碼要全面處理。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第18頁,共61頁Page 18 , Total61
說明:一個函數(標準庫中的函數/第三方庫函數/用戶定義的函數)可以提供一些指示錯誤發生的方法。這能夠經過使用錯誤標記、特殊的返回數據或者其餘手段,無論何時函數提供了這樣的機制,調用程序應該在函數返回時馬上檢查錯誤指示。
示例:下面的代碼致使宕機
FILE *fp = fopen( "./writeAlarmLastTime.log","r");
if(fp == NULL)
{
return;
}
char buff[128] = "";
fscanf(fp,「%s」, buff); /* 讀取最新的告警時間;因爲文件writeAlarmLastTime.log爲空,致使buff爲空 */
fclose(fp);
long fileTime = getAlarmTime(buff); /* 解析獲取最新的告警時間;getAlarmTime函數未檢查buff指針,致使宕機 */
正確寫法:
FILE *fp = fopen( "./writeAlarmLastTime.log","r");
if(fp == NULL)
{
return;
}
char buff[128] = "";
if (fscanf(fp,「%s」,buff) == EOF) //檢查函數fscanf的返回值,確保讀到數據
{
fclose(fp);
return;
}
fclose(fp);
long fileTime = getAlarmTime(buff); //解析獲取最新的告警時間;
規則2.6 設計高扇入,合理扇出(小於7)的函數。
說明:扇出是指一個函數直接調用(控制)其它函數的數目,而扇入是指有多少上級函數調用它。
扇出過大,代表函數過度複雜,須要控制和協調過多的下級函數;而扇出太小,例如:老是1,代表函數的調用層次可能過多,這樣不利於程序閱讀和函數結構的分析,而且程序運行時會對系統資源如堆棧空間等形成壓力。一般函數比較合理的扇出(調度函數除外)一般是3~5。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第19頁,共61頁Page 19 , Total61
扇出太大,通常是因爲缺少中間層次,可適當增長中間層次的函數。扇出過小,可把下級函數進一步分解多個函數,或合併到上級函數中。固然分解或合併函數時,不能改變要實現的功能,也不能違背函數間的獨立性。
扇入越大,代表使用此函數的上級函數越多,這樣的函數使用效率高,但不能違背函數間的獨立性而單純地追求高扇入。公共模塊中的函數及底層函數應該有較高的扇入。
較良好的軟件結構一般是頂層函數的扇出較高,中層函數的扇出較少,而底層函數則扇入到公共模塊中。
延伸閱讀材料:扇入(Fan-in)和扇出(Fan-out)是Henry和Kafura在1981年引入,用來講明模塊間的耦合(coupling),後面人們擴展到函數/方法、模塊/類、包等。
The Fan-in (Informational fan-in) metric measures the fan-in of a module. The fan-in of a module A is the number of modules that pass control into module A.
The Fan-out metric measures the number of the number of modules that are called by a given module.
規則2.7 廢棄代碼(沒有被調用的函數和變量)要及時清除。
說明:程序中的廢棄代碼不只佔用額外的空間,並且還經常影響程序的功能與性能,極可能給程序的測試、維護等形成沒必要要的麻煩。
建議2.1 函數不變參數使用const。
說明:不變的值更易於理解/跟蹤和分析,把const做爲默認選項,在編譯時會對其進行檢查,使代碼更牢固/更安全。
示例:C99標準 7.21.4.4 中strncmp 的例子,不變參數聲明爲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 == '\0')
{
return 0;
}
}
return 0;
}
延伸閱讀:pc-lint 8.0的幫助材料(pc-lint.pdf)11.4 const Checking
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第20頁,共61頁Page 20 , Total61
建議2.2 函數應避免使用全局變量、靜態局部變量和I/O操做,不可避免的地方應集中使用。
說明:帶有內部「存儲器」的函數的功能多是不可預測的,由於它的輸出可能取決於內部存儲器(如某標記)的狀態。這樣的函數既不易於理解又不利於測試和維護。在C語言中,函數的static局部變量是函數的內部存儲器,有可能使函數的功能不可預測,然而,當某函數的返回值爲指針類型時,則必須是static的局部變量的地址做爲返回值,若爲auto類,則返回爲錯針。
示例:以下函數,其返回值(即功能)是不可預測的。
unsigned int integer_sum( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0;// 注意,是static類型的。
// 若改成auto類型,則函數即變爲可預測。
for (index = 1; index <= base; index++)
{
sum += index;
}
return sum;
}
延伸閱讀材料:erlang語言中關於dirty的概念,函數式語言的優點
建議2.3 檢查函數全部非參數輸入的有效性,如數據文件、公共變量等。
說明:函數的輸入主要有兩種:一種是參數輸入;另外一種是全局變量、數據文件的輸入,即非參數輸入。函數在使用輸入參數以前,應進行有效性檢查。
示例:下面的代碼致使宕機
hr = root_node->get_first_child(&log_item); // list.xml 爲空,致使讀出log_item爲空
…..
hr = log_item->get_next_sibling(&media_next_node); // log_item爲空,致使宕機
正確寫法:確保讀出的內容非空。
hr = root_node->get_first_child(&log_item);
…..
if (log_item == NULL) //確保讀出的內容非空
{
return retValue;
}
hr = log_item->get_next_sibling(&media_next_node);
建議2.4 函數的參數個數不超過5個。
說明:函數的參數過多,會使得該函數易於受外部(其餘部分的代碼)變化的影響,從而影響維護工做。函數的參數過多同時也會增大測試的工做量。
函數的參數個數不要超過5個,若是超過了建議拆分爲不一樣函數。
建議2.5 除打印類函數外,不要使用可變長參函數。
說明:可變長參函數的處理過程比較複雜容易引入錯誤,並且性能也比較低,使用過多的可變長參函
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第21頁,共61頁Page 21 , Total61
數將致使函數的維護難度大大增長。
建議2.6 在源文件範圍內聲明和定義的全部函數,除非外部可見,不然應該增長static關鍵字。
說明:若是一個函數只是在同一文件中的其餘地方調用,那麼就用static聲明。使用static確保只是在聲明它的文件中是可見的,而且避免了和其餘文件或庫中的相同標識符發生混淆的可能性。
建議定義一個STATIC宏,在調試階段,將STATIC定義爲static,版本發佈時,改成空,以便於後續的打熱補丁等操做。
#ifdef _DEBUG
#define STATIC static
#else
#define STATIC
#endif
3 標識符命名與定義
3.1 通用命名規則
目前比較使用的以下幾種命名風格:
unix like風格:單詞用小寫字母,每一個單詞直接用下劃線„_‟分割,例如text_mutex,kernel_text_address。
Windows風格:大小寫字母混用,單詞連在一塊兒,每一個單詞首字母大寫。不過Windows風格若是遇到大寫專有用語時會有些彆扭,例如命名一個讀取RFC文本的函數,命令爲ReadRFCText,看起來就沒有unix like的read_rfc_text清晰了。
匈牙利命名法是計算機程序設計中的一種命名規則,用這種方法命名的變量顯示了其數據類型。匈牙利命名主要包括三個部分:基本類型、一個或更多的前綴、一個限定詞。這種命令法最初在20世紀80年代的微軟公司普遍使用,並在win32API和MFC庫中普遍的使用,但匈牙利命名法存在較多的爭議,例如:.NET Framework,微軟新的軟件開發平臺,除了接口類型通常不適用匈牙利命名法。.NET Framework指導方針建議程序員不要用匈牙利命名法,可是沒有指明不要用系統匈牙利命名法仍是匈牙利應用命名法,或者是二者都不要用。與此對比,Java的標準庫中鏈接口類型也不加前綴。(來源http://zh.wikipedia.org/wiki/%E5%8C%88%E7%89%99%E5%88%A9%E5%91%BD%E5%90%8D%E6%B3%95)
匈牙利命名法更多的信息見http://en.wikipedia.org/wiki/Hungarian_notation。
標識符的命名規則從來是一個敏感話題,典型的命名風格如unix風格、windows風格等等,歷來沒法達成共識。實際上,各類風格都有其優點也有其劣勢,並且每每和我的的審美觀有關。咱們對標識符定義主要是爲了讓團隊的代碼看起來儘量統一,有利於代碼的後續閱讀和修改,產品能夠根據本身的實際須要指定命名風格,規範中再也不作統一的規定。
原則3.1 標識符的命名要清晰、明瞭,有明確含義,同時使用完整的單詞或你們基本能夠理解的縮寫,避免令人產生誤解。
說明:儘量給出描述性名稱,不要節約空間,讓別人很快理解你的代碼更重要。
示例:好的命名:
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第22頁,共61頁Page 22 , Total61
int error_number;
int number_of_completed_connection;
很差的命名:使用模糊的縮寫或隨意的字符:
int n;
int nerr;
int n_comp_conns;
原則3.2 除了常見的通用縮寫之外,不使用單詞縮寫,不得使用漢語拼音。
說明:較短的單詞可經過去掉「元音」造成縮寫,較長的單詞可取單詞的頭幾個字母造成縮寫,一些單詞有你們公認的縮寫,經常使用單詞的縮寫必須統一。協議中的單詞的縮寫與協議保持一致。對於某個系統使用的專用縮寫應該在注視或者某處作統一說明。
示例:一些常見能夠縮寫的例子:
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.1 產品/項目組內部應保持統一的命名風格。
說明:Unix like和windows like風格均有其擁躉,產品應根據本身的部署平臺,選擇其中一種,並在產品內部保持一致。
例外:即便產品以前使用匈牙利命名法,新代碼也不該當使用。
建議3.1 用正確的反義詞組命名具備互斥意義的變量或相反動做的函數等。
示例:
add/remove begin/end create/destroy
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第23頁,共61頁Page 23 , Total61
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
建議3.2 儘可能避免名字中出現數字編號,除非邏輯上的確須要編號。
示例:以下命名,令人產生疑惑。
#define EXAMPLE_0_TEST_
#define EXAMPLE_1_TEST_
應改成有意義的單詞命名
#define EXAMPLE_UNIT_TEST_
#define EXAMPLE_ASSERT_TEST_
建議3.3 標識符前不該添加模塊、項目、產品、部門的名稱做爲前綴。
說明:不少已有代碼中已經習慣在文件名中增長模塊名,這種寫法相似匈牙利命名法,致使文件名不可讀,而且帶來帶來以下問題:
第一眼看到的是模塊名,而不是真正的文件功能,阻礙閱讀;
文件名太長;
文件名和模塊綁定,不利於維護和移植。若foo.c進行重構後,從a模塊挪到b模塊,若foo.c中有模塊名,則須要將文件名從a_module_foo.c改成b_module_foo.c
建議3.4 平臺/驅動等適配代碼的標識符命名風格保持和平臺/驅動一致。
說明:涉及到外購芯片以及配套的驅動,這部分的代碼變更(包括爲產品作適配的新增代碼),應該保持原有的風格。
建議3.5 重構/修改部分代碼時,應保持和原有代碼的命名風格一致。
說明:根據源代碼現有的風格繼續編寫代碼,有利於保持整體一致。
3.2 文件命名規則
建議3.6 文件命名統一採用小寫字符。
說明:由於不一樣系統對文件名大小寫處理會不一樣(如MS的DOS、Windows系統不區分大小寫,可是Linux系統則區分),因此代碼文件命名建議統一採用全小寫字母命名。
3.3 變量命名規則
規則3.2 全局變量應增長「g_」前綴。
規則3.3 靜態變量應增長「s_」前綴。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第24頁,共61頁Page 24 , Total61
說明:增長g_前綴或者s_前綴,緣由以下:
首先,全局變量十分危險,經過前綴使得全局變量更加醒目,促使開發人員對這些變量的使用更加當心。
其次,從根本上說,應當儘可能不使用全局變量,增長g_和s_前綴,會使得全局變量的名字顯得很醜陋,從而促使開發人員儘可能少使用全局變量。
規則3.4 禁止使用單字節命名變量,但容許定義i、j、k做爲局部循環變量。
建議3.7 不建議使用匈牙利命名法。
說明:變量命名須要說明的是變量的含義,而不是變量的類型。在變量命名前增長類型說明,反而下降了變量的可讀性;更麻煩的問題是,若是修改了變量的類型定義,那麼全部使用該變量的地方都須要修改。
匈牙利命名法源於微軟,然而卻被不少人以訛傳訛的使用。而如今即便是微軟也再也不推薦使用匈牙利命名法。從來對匈牙利命名法的一大詬病,就是致使了變量名難以閱讀,這和本規範的指導思想也有衝突,因此本規範特地強調,變量命名不該採用匈牙利命名法,而應想法使變量名爲一個有意義的詞或詞組,方便代碼的閱讀。
建議3.8 使用名詞或者形容詞+名詞方式命名變量。
3.4 函數命名規則
建議3.9 函數命名應以函數要執行的動做命名,通常採用動詞或者動詞+名詞的結構。
示例:找到當前進程的當前目錄
DWORD GetCurrentDirectory( DWORD BufferLength, LPTSTR Buffer );
建議3.10 函數指針除了前綴,其餘按照函數的命名規則命名。
3.5 宏的命名規則
規則3.5 對於數值或者字符串等等常量的定義,建議採用全大寫字母,單詞之間加下劃線„_‟的方式命名(枚舉一樣建議使用此方式定義)。
示例:
#define PI_ROUNDED 3.14
規則3.6 除了頭文件或編譯開關等特殊標識定義,宏定義不能使用下劃線„_‟開頭和結尾。
說明:通常來講,‟_‟開頭、結尾的宏都是一些內部的定義,ISO/IEC 9899(俗稱C99)中有以下的描述(6.10.8 Predefined macro names):
None of these macro names(這裏上面是一些內部定義的宏的描述), 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 followed by an uppercase letter or a second underscore.
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第25頁,共61頁Page 25 , Total61
延伸閱讀材料:《代碼大全第2版》(Steve McConnell 著 金戈/湯凌/陳碩/張菲 譯 電子工業出版社 2006年3月)"第11章變量命的力量"。
4 變量
原則4.1 一個變量只有一個功能,不能把一個變量用做多種用途。
說明:一個變量只用來表示一個特定功能,不能把一個變量做多種用途,即同一變量取值不一樣時,其表明的意義也不一樣。
示例:具備兩種功能的反例
WORD DelRelTimeQue(void)
{
WORD Locate;
Locate = 3;
Locate = DeleteFromQue(Locate); /* Locate具備兩種功能:位置和函數DeleteFromQue的返回值 */
return Locate;
}
正確作法:使用兩個變量
WORD DelRelTimeQue(void)
{
WORD Ret;
WORD Locate;
Locate = 3;
Ret = DeleteFromQue(Locate);
return Ret;
}
原則4.2 結構功能單一;不要設計面面俱到的數據結構。
說明:相關的一組信息纔是構成一個結構體的基礎,結構的定義應該能夠明確的描述一個對象,而不是一組相關性不強的數據的集合。
設計結構時應力爭使結構表明一種現實事務的抽象,而不是同時表明多種。結構中的各元素應表明同一事務的不一樣側面,而不該把描述沒有關係或關係很弱的不一樣事務的元素放到同一結構中。
示例:以下結構不太清晰、合理。
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 */
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第26頁,共61頁Page 26 , Total61
/* 0 - FEMALE; 1 - MALE */
unsigned char teacher_name[32]; /* the student teacher's name */
unsigned char teacher_sex; /* his teacher sex */
} STUDENT;
若改成以下,會更合理些。
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;
原則4.3 不用或者少用全局變量。
說明:單個文件內部能夠使用static的全局變量,能夠將其理解爲類的私有成員變量。
全局變量應該是模塊的私有數據,不能做用對外的接口使用,使用static類型定義,能夠有效防止外部文件的非正常訪問,建議定義一個STATIC宏,在調試階段,將STATIC定義爲static,版本發佈時,改成空,以便於後續的打補丁等操做。
#ifdef _DEBUG
#define STATIC static
#else
#define STATIC
#endif
直接使用其餘模塊的私有數據,將使模塊間的關係逐漸走向「剪不斷理還亂」的耦合狀態,這種情形是不容許的。
規則4.1 防止局部變量與全局變量同名。
說明:儘管局部變量和全局變量的做用域不一樣而不會發生語法錯誤,但容易令人誤解。
規則4.2 通信過程當中使用的結構,必須注意字節序。
說明:通信報文中,字節序是一個重要的問題,我司設備使用的cpu類型複雜多樣,大小端、32位/64位的處理器也都有,若是結構會在報文交互過程當中使用,必須考慮字節序問題。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第27頁,共61頁Page 27 , Total61
因爲位域在不一樣字節序下,表現看起來差異更大,因此更須要注意。
對於這種跨平臺的交互,數據成員發送前,都應該進行主機序到網絡序的轉換;接收時,也必須進行網絡序到主機序的轉換。
規則4.3 嚴禁使用未經初始化的變量做爲右值。
說明:堅持建議4.3(在首次使用前初始化變量,初始化的地方離使用的地方越近越好。)能夠有效避免未初始化錯誤。
建議4.1 構造僅有一個模塊或函數能夠修改、建立,而其他有關模塊或函數只訪問的全局變量,防止多個不一樣模塊或函數均可以修改、建立同一全局變量的現象。
說明:下降全局變量耦合度。
建議4.2 使用面向接口編程思想,經過API訪問數據:若是本模塊的數據須要對外部模塊開放,應提供接口函數來設置、獲取,同時注意全局數據的訪問互斥。
說明:避免直接暴露內部數據給外部模型使用,是防止模塊間耦合最簡單有效的方法。
定義的接口應該有比較明確的意義,好比一個風扇管理功能模塊,有自動和手動工做模式,那麼設置、查詢工做模塊就能夠定義接口爲SetFanWorkMode,GetFanWorkMode;查詢轉速就能夠定義爲GetFanSpeed;風扇支持節能功能開關,能夠定義EnabletFanSavePower等等。
建議4.3 在首次使用前初始化變量,初始化的地方離使用的地方越近越好。
說明:未初始化變量是C和C++程序中錯誤的常見來源。在變量首次使用前確保正確初始化。
在較好的方案中,變量的定義和初始化要作到親密無間。
示例:
//不可取的初始化:無心義的初始化
int speedup_factor = 0;
if (condition)
{
speedup_factor = 2;
}
else
{
speedup_factor = -1;
}
//不可取的初始化:初始化和聲明分離
int speedup_factor;
if (condition)
{
speedup_factor = 2;
}
else
{
speedup_factor = -1;
}
//較好的初始化:使用默認有意義的初始化
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第28頁,共61頁Page 28 , Total61
int speedup_factor = -1;
if (condition)
{
speedup_factor = 2;
}
//較好的初始化使用?:減小數據流和控制流的混合
int speedup_factor = condition?2:-1;
//較好的初始化:使用函數代替複雜的計算流
int speedup_factor = ComputeSpeedupFactor();
建議4.4 明確全局變量的初始化順序,避免跨模塊的初始化依賴。
說明:系統啓動階段,使用全局變量前,要考慮到該全局變量在何時初始化,使用全局變量和初始化全局變量,二者之間的時序關係,誰先誰後,必定要分析清楚,否則後果每每是低級而又災難性的。
建議4.5 儘可能減小沒有必要的數據類型默認轉換與強制轉換。
說明:當進行數據類型強制轉換時,其數據的意義、轉換後的取值等都有可能發生變化,而這些細節若考慮不周,就頗有可能留下隱患。
示例:以下賦值,多數編譯器不產生告警,但值的含義仍是稍有變化。
char ch;
unsigned short int exam;
ch = -1;
exam = ch; // 編譯器不產生告警,此時exam爲0xFFFF。
5 宏、常量
規則5.1 用宏定義表達式時,要使用完備的括號。
說明:由於宏只是簡單的代碼替換,不會像函數同樣先將參數計算後,再傳遞。
示例:以下定義的宏都存在必定的風險
#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))
這是由於:
若是定義#define RECTANGLE_AREA(a, b) a * b 或#define RECTANGLE_AREA(a, b) (a * b)
則c/RECTANGLE_AREA(a, b) 將擴展成c/a * b , c 與b 本應該是除法運算,結果變成了乘法運算,形成錯誤。
若是定義#define RECTANGLE_AREA(a, b) (a) * (b)
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第29頁,共61頁Page 29 , Total61
則RECTANGLE_AREA(c + d, e + f)將擴展成:(c + d * e + f), d與e 先運算,形成錯誤。
規則5.2 將宏所定義的多條表達式放在大括號中。
說明:更好的方法是多條語句寫成do while(0)的方式。
示例:看下面的語句,只有宏的第一條表達式被執行。
#define FOO(x) \
printf("arg is %d\n", x); \
do_something_useful(x);
爲了說明問題,下面for語句的書寫稍不符規範
for (blah = 1; blah < 10; blah++)
FOO(blah)
用大括號定義的方式能夠解決上面的問題:
#define FOO(x) { \
printf("arg is %s\n", x); \
do_something_useful(x); \
}
可是若是有人這樣調用:
if (condition == 1)
FOO(10);
else
FOO(20);
那麼這個宏仍是不能正常使用,因此必須這樣定義才能避免各類問題:
#define FOO(x) do { \
printf("arg is %s\n", x); \
do_something_useful(x); \
} while(0)
用do-while(0)方式定義宏,徹底不用擔憂使用者如何使用宏,也不用給使用者加什麼約束。
規則5.3 使用宏時,不容許參數發生變化。
示例:以下用法可能致使錯誤。
#define SQUARE(a) ((a) * (a))
int a = 5;
int b;
b = SQUARE(a++); // 結果:a = 7,即執行了兩次增。
正確的用法是:
b = SQUARE(a);
a++; // 結果:a = 6,即只執行了一次增。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第30頁,共61頁Page 30 , Total61
同時也建議即便函數調用,也不要在參數中作變量變化操做,由於可能引用的接口函數,在某個版本升級後,變成了一個兼容老版本所作的一個宏,結果可能不可預知。
規則5.4 不容許直接使用魔鬼數字。
說明:使用魔鬼數字的弊端:代碼難以理解;若是一個有含義的數字多處使用,一旦須要修改這個數值,代價慘重。
使用明確的物理狀態或物理意義的名稱能增長信息,並能提供單一的維護點。
解決途徑:
對於局部使用的惟一含義的魔鬼數字,能夠在代碼周圍增長說明註釋,也能夠定義局部const變量,變量命名自注釋。
對於普遍使用的數字,必須定義const全局變量/宏;一樣變量/宏命名應是自注釋的。
0做爲一個特殊的數字,做爲通常默認值使用沒有歧義時,不用特別定義。
建議5.1 除非必要,應儘量使用函數代替宏。
說明:宏對比函數,有一些明顯的缺點:
宏缺少類型檢查,不如函數調用檢查嚴格。
宏展開可能會產生意想不到的反作用,如#define SQUARE(a) (a) * (a)這樣的定義,若是是SQUARE(i++),就會致使i被加兩次;若是是函數調用double square(double a) {return a * a;}則不會有此反作用。
以宏形式寫的代碼難以調試難以打斷點,不利於定位問題。
宏若是調用的不少,會形成代碼空間的浪費,不如函數空間效率高。
示例:下面的代碼沒法獲得想要的結果:
#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\n", MAX_MACRO(++a, b));
printf("FUNC : max of a and b is: %d\n", MAX_FUNC(a, b));
return 0;
}
上面宏代碼調用中,結果是(a < b),因此a只加了一次,因此最終的輸出結果是:
MACRO: max of a and b is: -1
FUNC : max of a and b is: 2
建議5.2 常量建議使用const定義代替宏。
說明: 「儘可能用編譯器而不用預處理」,由於#define常常被認爲好象不是語言自己的一部分。看下面的語句:
#define ASPECT_RATIO 1.653
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第31頁,共61頁Page 31 , Total61
編譯器會永遠也看不到ASPECT_RATIO這個符號名,由於在源碼進入編譯器以前,它會被預處理程序去掉,因而ASPECT_RATIO不會加入到符號列表中。若是涉及到這個常量的代碼在編譯時報錯,就會很使人費解,由於報錯信息指的是1.653,而不是ASPECT_RATIO。若是ASPECT_RATIO不是在你本身寫的頭文件中定義的,你就會奇怪1.653是從哪裏來的,甚至會花時間跟蹤下去。這個問題也會出如今符號調試器中,由於一樣地,你所寫的符號名不會出如今符號列表中。
解決這個問題的方案很簡單:不用預處理宏,定義一個常量:
const double ASPECT_RATIO = 1.653;
這種方法頗有效,但有兩個特殊狀況要注意。首先,定義指針常量時會有點不一樣。由於常量定義通常是放在頭文件中(許多源文件會包含它),除了指針所指的類型要定義成const外,重要的是指針也常常要定義成const。例如,要在頭文件中定義一個基於char*的字符串常量,你要寫兩次const:
const char * const authorName = "Scott Meyers";
延伸閱讀材料:關於const和指針的使用,這裏摘錄兩段ISO/IEC 9899(俗稱C99)的描述:
The following pair of declarations demonstrates the difference between a "variable pointer to a constant value" and a "constant pointer to a variable value".
const int *ptr_to_constant;
int *const constant_ptr;
The contents of any object pointed to by ptr_to_constant shall not be modified through that pointer,but ptr_to_constant itself may be changed to point to another object. Similarly, the contents of the intpointed to by constant_ptrmay be modified, but constant_ptritself shall always point to the same location.
The declaration of the constant pointer constant_ptr may be clarified by including a definition for the type "pointer to int".
typedef int *int_ptr;
const int_ptr constant_ptr;
declares constant_ptras an object that has type "const-qualified pointer to int".
建議5.3 宏定義中儘可能不使用return、goto、continue、break等改變程序流程的語句。
說明:若是在宏定義中使用這些改變流程的語句,很容易引發資源泄漏問題,使用者很難本身察覺。
示例:在某頭文件中定義宏CHECK_AND_RETURN:
#define CHECK_AND_RETURN(cond, ret) {if (cond == NULL_PTR) {return ret;}}
而後在某函數中使用(只說明問題,代碼並不完整):
pMem1 = VOS_MemAlloc(...);
CHECK_AND_RETURN(pMem1 , ERR_CODE_XXX)
pMem2 = VOS_MemAlloc(...);
CHECK_AND_RETURN(pMem2 , ERR_CODE_XXX) /*此時若是pMem2==NULL_PTR,則pMem1未釋放函數就返回了,形成內存泄漏。*/
因此說,相似於CHECK_AND_RETURN這些宏,雖然能使代碼簡潔,可是隱患很大,使用須謹慎。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第32頁,共61頁Page 32 , Total61
6 質量保證
原則6.1 代碼質量保證優先原則
(1)正確性,指程序要實現設計要求的功能。
(2)簡潔性,指程序易於理解而且易於實現。
(3)可維護性,指程序被修改的能力,包括糾錯、改進、新需求或功能規格變化的適應能力。
(4)可靠性,指程序在給定時間間隔和環境條件下,按設計要求成功運行程序的機率。
(5)代碼可測試性,指軟件發現故障並隔離、定位故障的能力,以及在必定的時間和成本前提下,進行測試設計、測試執行的能力。
(6)代碼性能高效,指是儘量少地佔用系統資源,包括內存和執行時間。
(7)可移植性,指爲了在原來設計的特定環境以外運行,對系統進行修改的能力。
(8)我的表達方式/我的方便性,指我的編程習慣。
原則6.2 要時刻注意易混淆的操做符。
說明:包括易混淆和的易用錯操做符
一、易混淆的操做符
C語言中有些操做符很容易混淆,編碼時要很是當心。
賦值操做符「=」 邏輯操做符「==」
關係操做符「<」 位操做符"<<"
關係操做符「>」 位操做符「>>」
邏輯操做符「||」 位操做符"|"
邏輯操做符「&&」 位操做符"&"
邏輯操做符"!" 位操做符「~」
二、易用錯的操做符
(1) 除操做符"/"
當除操做符「/」的運算量是整型量時,運算結果也是整型。
如:1/2=0
(2)求餘操做符"%"
求餘操做符"%"的運算量只能是整型。
如:5%2=1,而5.0%2是錯誤的。
(3)自加、自減操做符「++」、「--」
示例1
k = 5;
x = k++;
執行後,x = 5,k = 6
示例2
k = 5;
x = ++k;
執行後,x = 6,k = 6
示例3
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第33頁,共61頁Page 33 , Total61
k = 5;
x = k--;
執行後,x = 5,k = 4
示例4
k = 5;
x = --k;
執行後,x = 4,k = 4
原則6.3 必須瞭解編譯系統的內存分配方式,特別是編譯系統對不一樣類型的變量的內存分配規則,如局部變量在何處分配、靜態變量在何處分配等。
原則6.4 不只關注接口,一樣要關注實現。
說明:這個原則看似和「面向接口」編程思想相悖,可是實現每每會影響接口,函數所能實現的功能,除了和調用者傳遞的參數相關,每每還受制於其餘隱含約束,如:物理內存的限制,網絡情況,具體看「抽象漏洞原則」。
延伸閱讀材料: http://local.joelonsoftware.com/mediawiki/index.php?title=Chinese_%28Simplified%29&oldid=9699
規則6.1 禁止內存操做越界。
說明:內存操做主要是指對數組、指針、內存地址等的操做。內存操做越界是軟件系統主要錯誤之一,後果每每很是嚴重,因此當咱們進行這些操做時必定要仔細當心。
示例:使用itoa()將整型數轉換爲字符串時:
char TempShold[10] ;
itoa(ProcFrecy,TempShold, 10); /* 數據庫刷新間隔設爲值1073741823時,系統監控後臺coredump,監控前臺拋異常。*/
TempShold是以‘\0’結尾的字符數組,只能存儲9個字符,而ProcFrecy的最大值可達到10位,致使符數組TempShold越界。
正確寫法:一個int(32位)在-2147483647~2147483648之間,將數組TempShold設置成12位。
char TempShold[12] ;
itoa(ProcFrecy,TempShold,10);
堅持下列措施能夠避免內存越界:
數組的大小要考慮最大狀況,避免數組分配空間不夠。
避免使用危險函數sprintf /vsprintf/strcpy/strcat/gets操做字符串,使用相對安全的函數snprintf/strncpy/strncat/fgets代替。
使用memcpy/memset時必定要確保長度不要越界
字符串考慮最後的’\0’, 確保全部字符串是以’\0’結束
指針加減操做時,考慮指針類型長度
數組下標進行檢查
使用時sizeof或者strlen計算結構/字符串長度,避免手工計算
延伸閱讀材料: 《公司常見軟件編程低級錯誤:內存越界.ppt》
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第34頁,共61頁Page 34 , Total61
規則6.2 禁止內存泄漏。
說明:內存和資源(包括定時器/文件句柄/Socket/隊列/信號量/GUI等各類資源)泄漏是常見的錯誤。示例:異常出口處沒有釋放內存
MsgDBDEV = (PDBDevMsg)GetBuff( sizeof( DBDevMsg ), __LINE__);
if (MsgDBDEV == NULL)
{
return;
}
MsgDBAppToLogic = (LPDBSelfMsg)GetBuff( sizeof(DBSelfMsg), __LINE__ );
if ( MsgDBAppToLogic == NULL )
{
return; //MsgDB_DEV指向的內存丟失
}
堅持下列措施能夠避免內存泄漏:
異常出口處檢查內存、定時器/文件句柄/Socket/隊列/信號量/GUI等資源是否所有釋放
刪除結構指針時,必須從底層向上層順序刪除
使用指針數組時,確保在釋放數組時,數組中的每一個元素指針是否已經提早被釋放了
避免重複分配內存
當心使用有return、break語句的宏,確保前面資源已經釋放
檢查隊列中每一個成員是否釋放
延伸閱讀材料: 《公司常見軟件編程低級錯誤:內存泄漏.ppt》
規則6.3 禁止引用已經釋放的內存空間。
說明:在實際編程過程當中,稍不留心就會出如今一個模塊中釋放了某個內存塊,而另外一模塊在隨後的某個時刻又使用了它。要防止這種狀況發生。
示例:一個函數返回的局部自動存儲對象的地址,致使引用已經釋放的內存空間
int* foobar (void)
{
int local_auto = 100;
return &local_auto;
}
堅持下列措施能夠避免引用已經釋放的內存空間:
內存釋放後,把指針置爲NULL;使用內存指針前進行非空判斷。
耦合度較強的模塊互相調用時,必定要仔細考慮其調用關係,防止已經刪除的對象被再次使用。
避免操做已發送消息的內存。
自動存儲對象的地址不該賦值給其餘的在第一個對象已經中止存在後仍然保持的對象(具備更大做用域的對象或者靜態對象或者從一個函數返回的對象)
延伸閱讀材料: 《公司常見軟件編程低級錯誤:野指針.ppt》
規則6.4 編程時,要防止差1錯誤。
說明:此類錯誤通常是因爲把「<=」誤寫成「<」或「>=」誤寫成「>」等形成的,由此引發的後果,不少狀況下是很嚴重的,因此編程時,必定要在這些地方當心。當編完程序後,應對這些操做符進行完全檢查。使用變量時要注意其邊界值的狀況。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第35頁,共61頁Page 35 , Total61
示例:如C語言中字符型變量,有效值範圍爲-128到127。故如下表達式的計算存在必定風險。
char ch = 127;
int sum = 200;
ch += 1; // 127爲ch的邊界值,再加將使ch上溢到-128,而不是128
sum += ch; // 故sum的結果不是328,而是72。
規則6.5 全部的if ... else if結構應該由else子句結束 ;switch語句必須有default分支。
建議6.1 函數中分配的內存,在函數退出以前要釋放。
說明:有不少函數申請內存,保存在數據結構中,要在申請處加上註釋,說明在何處釋放。
建議6.2 if語句儘可能加上else分支,對沒有else分支的語句要當心對待。
建議6.3 不要濫用goto語句。
說明:goto語句會破壞程序的結構性,因此除非確實須要,最好不使用goto語句。
能夠利用goto語句方面退出多重循環;同一個函數體內部存在大量相同的邏輯但又不方便封裝成函數的狀況下,譬如反覆執行文件操做,對文件操做失敗之後的處理部分代碼(譬如關閉文件句柄,釋放動態申請的內存等等),通常會放在該函數體的最後部分,再須要的地方就goto到那裏,這樣代碼反而變得清晰簡潔。實際也能夠封裝成函數或者封裝成宏,可是這麼作會讓代碼變得沒那麼直接明瞭。
示例:
int foo(void)
{
char* p1 = NULL;
char* p2 = NULL;
char* p3 = NULL;
int result = -1;
p1 = (char *)malloc(0x100);
if (p1 == NULL)
{
goto Exit0;
}
strcpy(p1, "this is p1");
p2 = (char *)malloc(0x100);
if (p2 == NULL)
{
goto Exit0;
}
strcpy(p2, "this is p2");
p3 = (char *)malloc(0x100);
if (p3 == NULL)
{
goto Exit0;
}
strcpy(p3, "this is p3");
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第36頁,共61頁Page 36 , Total61
result = 0;
Exit0:
free(p1); // C標準規定能夠free空指針
free(p2);
free(p3);
return result;
}
建議6.4 時刻注意表達式是否會上溢、下溢。
示例:以下程序將形成變量下溢。
unsigned char size ;
…
while (size-- >= 0) // 將出現下溢
{
... // program code
}
當size等於0時,再減不會小於0,而是0xFF,故程序是一個死循環。應以下修改。
char size; // 從unsigned char 改成char
…
while (size-- >= 0)
{
... // program code
}
7 程序效率
原則7.1 在保證軟件系統的正確性、簡潔、可維護性、可靠性及可測性的前提下,提升代碼效率。
本章節後面全部的規則和建議,都應在不影響前述可讀性等質量屬性的前提下實施。
說明:不能一味地追求代碼效率,而對軟件的正確、簡潔、可維護性、可靠性及可測性形成影響。
產品代碼中常常有以下代碼:
int foo()
{
if (異常條件)
{
異常處理;
return ERR_CODE_1;
}
if (異常條件)
{
異常處理;
return ERR_CODE_2;
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第37頁,共61頁Page 37 , Total61
}
正常處理;
return SUCCESS;
}
這樣的代碼看起來很清晰,並且也避免了大量的if else嵌套。可是從性能的角度來看,應該把執行機率較大的分支放在前面處理,因爲正常狀況下的執行機率更大,若首先考慮性能,應以下書寫:
int foo()
{
if (知足條件)
{
正常處理;
return SUCCESS;
}
else if (機率比較大的異常條件)
{
異常處理;
return ERR_CODE_1;
}
else
{
異常處理;
return ERR_CODE_2;
}
}
除非證實foo函數是性能瓶頸,不然按照本規則,應優先選用前面一種寫法。
以性能爲名,使設計或代碼更加複雜,從而致使可讀性更差,可是並無通過驗證的性能要求(好比實際的度量數據和目標的比較結果)做爲正當理由,本質上對程序沒有真正的好處。沒法度量的優化行爲其實根本不能使程序運行得更快。
記住:讓一個正確的程序更快速,比讓一個足夠快的程序正確,要容易得太多。大多數時候,不要把注意力集中在如何使代碼更快上,應首先關注讓代碼儘量地清晰易讀和更可靠。
原則7.2 經過對數據結構、程序算法的優化來提升效率。
建議7.1 將不變條件的計算移到循環體外。
說明:將循環中與循環無關,不是每次循環都要作的操做,移到循環外部執行。
示例一:
for (int i = 0; i < 10; i++ )
{
sum += i;
back_sum = sum;
}
對於此for循環來講語句「back_Sum = sum;」 不必每次都執行,只須要執行一次便可,所以能夠改成:
for (int i = 0; i < 10; i++ )
{
sum += i;
}
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第38頁,共61頁Page 38 , Total61
back_sum = sum;
示例二:
for (_UL i = 0; i < func_calc_max(); i++)
{
//process;
}
函數func_calc_max()不必每次都執行,只須要執行一次便可,所以能夠改成:
_UL max = func_calc_max();
for (_UL i = 0; i < max; i++)
{
//process;
}
建議7.2 對於多維大數組,避免來回跳躍式訪問數組成員。
示例:多維數組在內存中是從最後一維開始逐維展開連續存儲的。下面這個對二維數組訪問是以SIZE_B爲步長跳躍訪問,到尾部後再從頭(第二個成員)開始,依此類推。局部性比較差,當步長較大時,可能形成cache不命中,反覆從內存加載數據到cache。應該把i和j交換。
...
for (int i = 0; i < SIZE_B; i++)
{
for (int j = 0; j < SIZE_A; j++)
{
sum += x[j][i];
}
}
...
上面這段代碼,在 SIZE_B 數值較大時,效率可能會比下面的代碼低:
...
for (int i = 0; i < SIZE_B; i++)
{
for (int j = 0; j < SIZE_A; j++)
{
sum += x[i][j];
}
}
...
建議7.3 建立資源庫,以減小分配對象的開銷。
說明:例如,使用線程池機制,避免線程頻繁建立、銷燬的系統調用;使用內存池,對於頻繁申請、釋放的小塊內存,一次性申請一個大塊的內存,當系統申請內存時,從內存池獲取小塊內存,使用完
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第39頁,共61頁Page 39 , Total61
畢再釋放到內存池中,避免內存申請釋放的頻繁系統調用.
建議7.4 將屢次被調用的 「小函數」改成inline函數或者宏實現。
說明: 若是編譯器支持inline,能夠採用inline函數。不然能夠採用宏。
在作這種優化的時候必定要注意下面inline函數的優勢:其一編譯時不用展開,代碼SIZE小。其二能夠加斷點,易於定位問題,例如對於引用計數加減的時候。其三函數編譯時,編譯器會作語法檢查。三思然後行。
8 註釋
原則8.1 優秀的代碼能夠自我解釋,不經過註釋便可輕易讀懂。
說明:優秀的代碼不寫註釋也可輕易讀懂,註釋沒法把糟糕的代碼變好,須要不少註釋來解釋的代碼每每存在壞味道,須要重構。
示例:註釋不能消除代碼的壞味道:
/* 判斷m是否爲素數*/
/* 返回值:: 是素數,: 不是素數*/
int p(int m)
{
int k = sqrt(m);
for (int i = 2; i <= k; i++)
if (m % i == 0)
break; /* 發現整除,表示m不爲素數,結束遍歷*/
/* 遍歷中沒有發現整除的狀況,返回*/
if (i > k)
return 1;
/* 遍歷中沒有發現整除的狀況,返回*/
else
return 0;
}
重構代碼後,不須要註釋:
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;
}
原則8.2 註釋的內容要清楚、明瞭,含義準確,防止註釋二義性。
說明:有歧義的註釋反而會致使維護者更難看懂代碼,正如帶兩塊表反而不知道準確時間。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第40頁,共61頁Page 40 , Total61
示例:註釋與代碼相矛盾,註釋內容也不清楚,先後矛盾。
/* 上報網管時要求故障ID與恢復ID相一致*/
/* 所以在此由告警級別獲知是否是恢復ID */
/* 如果恢復ID則設置爲ClearId,不然設置爲AlarmId */
if (CLEAR_ALARM_LEVEL != RcData.level)
{
SetAlarmID(RcData.AlarmId);
}
else
{
SetAlarmID(RcData.ClearId);
}
正確作法:修改註釋描述以下:
/* 網管達成協議:上報故障ID與恢復ID由告警級別肯定,如果清除級別,ID設置爲ClearId,不然設爲AlarmId。*/
原則8.3 在代碼的功能、意圖層次上進行註釋,即註釋解釋代碼難以直接表達的意圖,而不是重複描述代碼。
說明:註釋的目的是解釋代碼的目的、功能和採用的方法,提供代碼之外的信息,幫助讀者理解代碼,防止不必的重複註釋信息。
對於實現代碼中巧妙的、晦澀的、有趣的、重要的地方加以註釋。
註釋不是爲了名詞解釋(what),而是說明用途(why)。
示例:以下注釋純屬多餘。
++i; /* increment i */
if (receive_flag) /* if receive_flag is TRUE */
以下這種無價值的註釋不該出現(空洞的笑話,可有可無的註釋)。
/* 時間有限,如今是:04,根原本不及想爲何,也沒人能幫我說清楚*/
而以下的註釋則給出了有用的信息:
/* 因爲xx編號網上問題,在xx狀況下,芯片可能存在寫錯誤,此芯片進行寫操做後,必須進行回讀校驗,若是回讀不正確,須要再重複寫-回讀操做,最多重複三次,這樣能夠解決絕大多數網上應用時的寫錯誤問題*/
int time = 0;
do
{
write_reg(some_addr, value);
time++;
} while ((read_reg(some_addr) != value) && (time < 3));
對於實現代碼中巧妙的、晦澀的、有趣的、重要的地方加以註釋,出彩的或複雜的代碼塊前要加註釋,如:
/* Divide result by two, taking into account that x contains the carry from the add. */
for (int i = 0; i < result->size(); i++)
{
x = (x << 8) + (*result)[i];
(*result)[i] = x >> 1;
x &= 1;
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第41頁,共61頁Page 41 , Total61
}
規則8.1 修改代碼時,維護代碼周邊的全部註釋,以保證註釋與代碼的一致性。再也不有用的註釋要刪除。
說明:不要將無用的代碼留在註釋中,隨時能夠從源代碼配置庫中找回代碼;即便只是想暫時排除代碼,也要留個標註,否則可能會忘記處理它。
規則8.2 文件頭部應進行註釋,註釋必須列出:版權說明、版本號、生成日期、做者姓名、工號、內容、功能說明、與其它文件的關係、修改日誌等,頭文件的註釋中還應有函數功能簡要說明。
說明:一般頭文件要對功能和用法做簡單說明,源文件包含了更多的實現細節或算法討論。
版權聲明格式:Copyright © Huawei Technologies Co., Ltd. 1998-2011. All rights reserved.
1998-2011根據實際須要能夠修改, 1998是文件首次建立年份,而2011是最新文件修改年份。
示例:下面這段頭文件的頭註釋比較標準,固然,並不侷限於此格式,但上述信息建議要包含在內。
/*************************************************
Copyright © Huawei Technologies Co., Ltd. 1998-2011. All rights reserved.
File name: // 文件名
Author: ID: Version: Date: // 做者、工號、版本及完成日期
Description: // 用於詳細說明此程序文件完成的主要功能,與其餘模塊
// 或函數的接口,輸出值、取值範圍、含義及參數間的控
// 制、順序、獨立或依賴等關係
Others: // 其它內容的說明
History: // 修改歷史記錄列表,每條修改記錄應包括修改日期、修改
// 者及修改內容簡述
1. Date:
Author: ID:
Modification:
2. ...
*************************************************/
規則8.3 函數聲明處註釋描述函數功能、性能及用法,包括輸入和輸出參數、函數返回值、可重入的要求等;定義處詳細描述函數功能和實現要點,如實現的簡要步驟、實現的理由、設計約束等。
說明:重要的、複雜的函數,提供外部使用的接口函數應編寫詳細的註釋。
規則8.4 全局變量要有較詳細的註釋,包括對其功能、取值範圍以及存取時注意事項等的說明。
示例:
/* 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 */
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第42頁,共61頁Page 42 , Total61
/* the function GetGTTransErrorCode() */ /* 使用方法*/
BYTE g_GTTranErrorCode;
規則8.5 註釋應放在其代碼上方相鄰位置或右方,不可放在下面。如放於上方則需與其上面的代碼用空行隔開,且與下方代碼縮進相同。
示例:
/* active statistic task number */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */
可按以下形式說明枚舉/數據/聯合結構。
/* 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*/
};
規則8.6 對於switch語句下的case語句,若是由於特殊狀況須要處理完一個case後進入下一個case處理,必須在該case語句處理完、下一個case語句前加上明確的註釋。
說明:這樣比較清楚程序編寫者的意圖,有效防止無端遺漏break語句。
示例(注意斜體加粗部分):
case CMD_FWD:
ProcessFwd();
/* now jump into case CMD_A */
case CMD_A:
ProcessA();
break;
//對於中間無處理的連續case,已能較清晰說明意圖,不強制註釋。
switch (cmd_flag)
{
case CMD_A:
case CMD_B:
{
ProcessCMD();
break;
}
……
}
規則8.7 避免在註釋中使用縮寫,除非是業界通用或子系統內標準化的縮寫。
規則8.8 同一產品或項目組統一註釋風格。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第43頁,共61頁Page 43 , Total61
建議8.1 避免在一行代碼或表達式的中間插入註釋。
說明:除非必要,不該在代碼或表達中間插入註釋,不然容易使代碼可理解性變差。
建議8.2 註釋應考慮程序易讀及外觀排版的因素,使用的語言如果中、英兼有的,建議多使用中文,除非能用很是流利準確的英文表達。對於有外籍員工的,由產品肯定註釋語言。
說明:註釋語言不統一,影響程序易讀性和外觀排版,出於對維護人員的考慮,建議使用中文。
建議8.3 文件頭、函數頭、全局常量變量、類型定義的註釋格式採用工具可識別的格式。
說明:採用工具可識別的註釋格式,例如doxygen格式,方便工具導出註釋造成幫助文檔。
以doxygen格式爲例,文件頭,函數和所有變量的註釋的示例以下:
文件頭註釋: /** * @file (本文件的文件名eg:mib.h) * @brief (本文件實現的功能的簡述) * @version 1.1 (版本聲明) * @author (做者,eg:張三) * @date (文件建立日期,eg:2010年12月15日) */
函數頭註釋: /** *@ Description:向接收方發送SET請求 * @param req - 指向整個SNMP SET 請求報文. * @param ind - 須要處理的subrequest 索引. * @return 成功:SNMP_ERROR_SUCCESS,失敗:SNMP_ERROR_COMITFAIL */
Int commit_set_request(Request *req, int ind);
全局變量註釋: /** 模擬的Agent MIB */ agentpp_simulation_mib * g_agtSimMib;
函數頭註釋建議寫到聲明處。並不是全部函數都必須寫註釋,建議針對這樣的函數寫註釋:重要的、複雜的函數,提供外部使用的接口函數。
延伸閱讀材料:
一、《代碼大全第2版》(Steve McConnell 著 金戈/湯凌/陳碩/張菲 譯 電子工業出版社 2006年3月)"第32章自說明代碼"。
二、《代碼整潔之道》(Robert C.Martin 著 韓磊 譯 人民郵電出版社2010年1月)第四章"註釋"。
三、《敏捷軟件開發:原則、模式與實踐》(Robert C.Martin 著 鄧輝 譯 清華大學出版社2003年9月)"第5章重構"。
四、《Doxygen中文手冊》(http://hi3ms.huawei.com/group/1735/files.html)。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第44頁,共61頁Page 44 , Total61
9 排版與格式
規則9.1 程序塊採用縮進風格編寫,每級縮進爲4個空格。
說明:當前各類編輯器/IDE都支持TAB鍵自動轉空格輸入,須要打開相關功能並設置相關功能。
編輯器/IDE若是有顯示TAB的功能也應該打開,方便及時糾正輸入錯誤。
IDE嚮導生成的代碼能夠不用修改。
宏定義、編譯開關、條件預處理語句能夠頂格(或使用自定義的排版方案,但產品/模塊內必須保持一致)。
規則9.2 相對獨立的程序塊之間、變量說明以後必須加空行。
示例:以下例子不符合規範。
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;
規則9.3 一條語句不能過長,如不能拆分須要分行寫。一行到底多少字符換行比較合適,產品能夠自行肯定。
說明:對於目前大多數的PC來講,132比較合適(80/132是VTY常見的行寬值);對於新PC寬屏顯示器較多的產品來講,能夠設置更大的值。
換行時有以下建議:
換行時要增長一級縮進,使代碼可讀性更好;
低優先級操做符處劃分新行;換行時操做符應該也放下來,放在新行首;
換行時建議一個完整的語句放在一行,不要根據字符數斷行
示例:
if ((temp_flag_var == TEST_FLAG)
&&(((temp_counter_var - TEST_COUNT_BEGIN) % TEST_COUNT_MODULE) >= TEST_COUNT_THRESHOLD))
{
// process code
}
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第45頁,共61頁Page 45 , Total61
規則9.4 多個短語句(包括賦值語句)不容許寫在同一行內,即一行只寫一條語句。
示例:
int a = 5; int b= 10; //很差的排版
較好的排版
int a = 5;
int b= 10;
規則9.5 if、for、do、while、case、switch、default等語句獨佔一行。
說明:執行語句必須用縮進風格寫,屬於if、for、do、while、case、switch、default等下一個縮進級別;
通常寫if、for、do、while等語句都會有成對出現的„{}‟,對此有以下建議能夠參考:
if、for、do、while等語句後的執行語句建議增長成對的„{}‟;
若是if/else配套語句中有一個分支有„{}‟,那麼令一個分支即便一行代碼也建議增長„{}‟;
添加„{‟的位置能夠在if等語句後,也能夠獨立佔下一行;獨立佔下一行時,能夠和if在一個縮進級別,也能夠在下一個縮進級別;可是若是if語句很長,或者已經有換行,建議„{‟使用獨佔一行的寫法。
規則9.6 在兩個以上的關鍵字、變量、常量進行對等操做時,它們之間的操做符以前、以後或者先後要加空格;進行非對等操做時,若是是關係密切的當即操做符(如->),後不該加空格。
說明:採用這種鬆散方式編寫代碼的目的是使代碼更加清晰。
在已經很是清晰的語句中沒有必要再留空格,如括號內側(即左括號後面和右括號前面)不須要加空格,多重括號間沒必要加空格,由於在C語言中括號已是最清晰的標誌了。
在長語句中,若是須要加的空格很是多,那麼應該保持總體清晰,而在局部不加空格。給操做符留空格時不要連續留兩個以上空格。
示例:
(1) 逗號、分號只在後面加空格
int a, b, c;
(2) 比較操做符, 賦值操做符"="、 "+=",算術操做符"+"、"%",邏輯操做符"&&"、"&",位域操做符"<<"、"^"等雙目操做符的先後加空格。
if (current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;
(3) "!"、"~"、"++"、"--"、"&"(地址操做符)等單目操做符先後不加空格。
*p = 'a'; // 內容操做"*"與內容之間
flag = !is_empty; // 非操做"!"與內容之間
p = &mem; // 地址操做"&" 與內容之間
i++; // "++","--"與內容之間
(4) "->"、"."先後不加空格。
p->id = pid; // "->"指針先後不加空格
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第46頁,共61頁Page 46 , Total61
(5) if、for、while、switch等與後面的括號間應加空格,使if等關鍵字更爲突出、明顯。
if (a >= b && c > d)
建議9.1 註釋符(包括„/*‟„//‟„*/‟)與註釋內容之間要用一個空格進行分隔。
說明:這樣能夠使註釋的內容部分更清晰。
如今不少工具均可以批量生成、刪除'//'註釋,這樣有空格也比較方便統一處理。
建議9.2 源程序中關係較爲緊密的代碼應儘量相鄰。
10 表達式
規則10.1 表達式的值在標準所容許的任何運算次序下都應該是相同的。
說明:除了少數操做符(函數調用操做符 ( )、&&、| |、? : 和 , (逗號)) 以外,子表達式所依據的運算次序是未指定的並會隨時更改。注意,運算次序的問題不能使用括號來解決,由於這不是優先級的問題。
將複合表達式分開寫成若干個簡單表達式,明確表達式的運算次序,就能夠有效消除非預期反作用。
一、自增或自減操做符
示例:
x = b[i] + i++;
b[i] 的運算是先於仍是後於 i ++ 的運算,表達式會產生不一樣的結果,把自增運算作爲單獨的語句,能夠避免這個問題。
x = b[i] + i;
i ++;
2﹑函數參數
說明:函數參數一般從右到左壓棧,但函數參數的計算次序不必定與壓棧次序相同。
示例:
x = func( i++, i);
應該修改代碼明確先計算第一個參數:
i++;
x = func(i, i);
三、函數指針
說明:函數參數和函數自身地址的計算次序未定義。
示例:
p->task_start_fn(p++);
求函數地址p與計算p++無關,結果是任意值。必須單獨計算p++:
p->task_start_fn(p);
p++;
4﹑函數調用
示例:
int g_var = 0;
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第47頁,共61頁Page 47 , Total61
int fun1()
{
g_var += 10;
return g_var;
}
int fun2()
{
g_var += 100;
return g_var;
}
int x = fun1() + fun2();
編譯器可能先計算fun1(),也可能先計算fun2(),因爲x的結果依賴於函數fun1()/fun2()的計算次序(fun1()/fun2()被調用時修改和使用了同一個全局變量),則上面的代碼存在問題。
應該修改代碼明確fun1/ fun2的計算次序:
int x = fun1();
x = x + fun2();
五、嵌套賦值語句
說明:表達式中嵌套的賦值能夠產生附加的反作用。不給這種能致使對運算次序的依賴提供任何機會的最好作法是,不要在表達式中嵌套賦值語句。
示例:
x = y = y = z / 3;
x = y = y++;
六、volatile訪問
說明:限定符volatile表示可能被其它途徑更改的變量,例如硬件自動更新的寄存器。編譯器不會優化對volatile變量的讀取。
示例:下面的寫法可能沒法實現做者預期的功能:
/* volume變量被定義爲volatile類型*/
UINT16 x = ( volume << 3 ) | volume; /* 在計算了其中一個子表達式的時候,volume的值可能已經被其它程序或硬件改變,致使另一個子表達式的計算結果非預期,可能沒法實現做者預期的功能*/
建議10.1 函數調用不要做爲另外一個函數的參數使用,不然對於代碼的調試、閱讀都不利。
說明:以下代碼不合理,僅用於說明當函數做爲參數時,因爲參數壓棧次數不是代碼能夠控制的,可能形成未知的輸出:
int g_var;
int fun1()
{
g_var += 10;
return g_var;
}
int fun2()
{
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第48頁,共61頁Page 48 , Total61
g_var += 100;
return g_var;
}
int main(int argc, char *argv[], char *envp[])
{
g_var = 1;
printf("func1: %d, func2: %d\n", fun1(), fun2());
g_var = 1;
printf("func2: %d, func1: %d\n", fun2(), fun1());
}
上面的代碼,使用斷點調試起來也比較麻煩,閱讀起來也不舒服,因此不要爲了節約代碼行,而寫這種代碼。
建議10.2 賦值語句不要寫在if等語句中,或者做爲函數的參數使用。
說明:由於if語句中,會根據條件依次判斷,若是前一個條件已經能夠斷定整個條件,則後續條件語句不會再運行,因此可能致使指望的部分賦值沒有獲得運行。
示例:
int main(int argc, char *argv[], char *envp[])
{
int a = 0;
int b;
if ((a == 0) || ((b = fun1()) > 10))
{
printf("a: %d\n", a);
}
printf("b: %d\n", b);
}
做用函數參數來使用,參數的壓棧順序不一樣可能致使結果未知。
看以下代碼,可否一眼看出輸出結果會是什麼嗎?好理解嗎?
int g_var;
int main(int argc, char *argv[], char *envp[])
{
g_var = 1;
printf("set 1st: %d, add 2nd: %d\n", g_var = 10, g_var++);
g_var = 1;
printf("add 1st: %d, set 2nd: %d\n", g_var++, g_var = 10);
}
建議10.3 用括號明確表達式的操做順序,避免過度依賴默認優先級。
說明:使用括號強調所使用的操做符,防止因默認的優先級與設計思想不符而致使程序出錯;同時使得代碼更爲清晰可讀,然而過多的括號會分散代碼使其下降了可讀性。下面是如何使用括號的建議。
1. 一元操做符,不須要使用括號
x = ~a; /* 一元操做符,不須要括號*/
x = -a; /* 一元操做符,不須要括號*/
2. 二元以上操做符,若是涉及多種操做符,則應該使用括號
x = a + b + c; /* 操做符相同,不須要括號*/
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第49頁,共61頁Page 49 , Total61
x = f ( a + b, c ) /* 操做符相同,不須要括號*/
if (a && b && c) /* 操做符相同,不須要括號*/
x = (a * 3) + c + d; /* 操做符不一樣,須要括號*/
x = ( a == b ) ? a : ( a –b ); /* 操做符不一樣,須要括號*/
3 .即便全部操做符都是相同的,若是涉及類型轉換或者量級提高,也應該使用括號控制計算的次序
如下代碼將3個浮點數相加:
/* 除了逗號(,),邏輯與(&&),邏輯或(||)以外,C標準沒有規定同級操做符是從左仍是從右開始計算,以上表達式存在種計算次序:f4 = (f1 + f2) + f3 或f4 = f1 + (f2 + f3),浮點數計算過程當中可能四捨五入,量級提高,計算次序的不一樣會致使f4的結果不一樣,以上表達式在不一樣編譯器上的計算結果可能不同,建議增長括號明確計算順序*/
f4 = f1 + f2 + f3;
.
建議10.4 賦值操做符不能使用在產生布爾值的表達式上。
說明:若是布爾值表達式須要賦值操做,那麼賦值操做必須在操做數以外分別進行。這能夠幫助避免=和= =的混淆,幫助咱們靜態地檢查錯誤。
示例:
x = y;
if (x != 0)
{
foo ();
}
不能寫成:
if (( x = y ) != 0)
{
foo ();
}
或者更壞的
if (x = y)
{
foo ();
}
11 代碼編輯、編譯
規則11.1 使用編譯器的最高告警級別,理解全部的告警,經過修改代碼而不是下降告警級別來消除全部告警。
說明:編譯器是你的朋友,若是它發出某個告警,這常常說明你的代碼中存在潛在的問題。
規則11.2 在產品軟件(項目組)中,要統一編譯開關、靜態檢查選項以及相應告警清除策略。
說明:若是必須禁用某個告警,應儘量單獨局部禁用,而且編寫一個清晰的註釋,說明爲何屏蔽。
某些語句經編譯/靜態檢查產生告警,但若是你認爲它是正確的,那麼應經過某種手段去掉告警信息。
規則11.3 本地構建工具(如PC-Lint)的配置應該和持續集成的一致。
說明:二者一致,避免通過本地構建的代碼在持續集成上構建失敗。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第50頁,共61頁Page 50 , Total61
規則11.4 使用版本控制(配置管理)系統,及時簽入經過本地構建的代碼,確保簽入的代碼不會影響構建成功。
說明:及時簽入代碼下降集成難度。
建議11.1 要當心地使用編輯器提供的塊拷貝功能編程。
12 可測性
原則12.1 模塊劃分清晰,接口明確,耦合性小,有明確輸入和輸出,不然單元測試實施困難。
說明:單元測試實施依賴於:
模塊間的接口定義清楚、完整、穩定;
模塊功能的有明確的驗收條件(包括:預置條件、輸入和預期結果);
模塊內部的關鍵狀態和關鍵數據能夠查詢,能夠修改;
模塊原子功能的入口惟一;
模塊原子功能的出口惟一;
依賴集中處理:和模塊相關的全局變量儘可能的少,或者採用某種封裝形式。
規則12.1 在同一項目組或產品組內,要有一套統一的爲集成測試與系統聯調準備的調測開關及相應打印函數,而且要有詳細的說明。
說明:本規則是針對項目組或產品組的。代碼至始至終只有一份代碼,不存在開發版本和測試版本的說法。測試與最終發行的版本是經過編譯開關的不一樣來實現的。而且編譯開關要規範統一。統一使用編譯開關來實現測試版本與發行版本的區別,通常不容許再定義其它新的編譯開關。
規則12.2 在同一項目組或產品組內,調測打印的日誌要有統一的規定。
說明:統一的調測日誌記錄便於集成測試,具體包括:
統一的日誌分類以及日誌級別;
經過命令行、網管等方式能夠配置和改變日誌輸出的內容和格式;
在關鍵分支要記錄日誌,日誌建議不要記錄在原子函數中,不然難以定位;
調試日誌記錄的內容須要包括文件名/模塊名、代碼行號、函數名、被調用函數名、錯誤碼、錯誤發生的環境等。
規則12.3 使用斷言記錄內部假設。
說明:斷言是對某種內部模塊的假設條件進行檢查,若是假設不成立,說明存在編程、設計錯誤。斷言能夠對在系統中隱藏很深,用其它手段極難發現的問題進行定位,從而縮短軟件問題定位時間,提升系統的可測性。
規則12.4 不能用斷言來檢查運行時錯誤。
說明:斷言是用來處理內部編程或設計是否符合假設;不能處理對於可能會發生的且必須處理的狀況要寫防錯程序,而不是斷言。如某模塊收到其它模塊或鏈路上的消息後,要對消息的合理性進行檢查,此過程爲正常的錯誤檢查,不能用斷言來實現。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第51頁,共61頁Page 51 , Total61
斷言的使用是有條件的。斷言只能用於程序內部邏輯的條件判斷,而不能用於對外部輸入數據的判斷,由於在網上實際運行時,是徹底有可能出現外部輸入非法數據的狀況。
建議12.1 爲單元測試和系統故障注入測試準備好方法和通道。
13 安全性
代碼的安全漏洞大都是由代碼缺陷致使,但不是全部代碼缺陷都有安全風險。理解安全漏洞產生的原理和如何進行安全編碼是減小軟件安全問題最直接有效的辦法。
原則13.1 對用戶輸入進行檢查。
說明:不能假定用戶輸入都是合法的,由於難以保證不存在惡意用戶,即便是合法用戶也可能因爲誤用誤操做而產生非法輸入。用戶輸入一般須要通過檢驗以保證安全,特別是如下場景:
用戶輸入做爲循環條件
用戶輸入做爲數組下標
用戶輸入做爲內存分配的尺寸參數
用戶輸入做爲格式化字符串
用戶輸入做爲業務數據(如做爲命令執行參數、拼裝sql語句、以特定格式持久化)
這些狀況下若是不對用戶數據作合法性驗證,極可能致使DOS、內存越界、格式化字符串漏洞、命令注入、SQL注入、緩衝區溢出、數據破壞等問題。
可採起如下措施對用戶輸入檢查:
用戶輸入做爲數值的,作數值範圍檢查
用戶輸入是字符串的,檢查字符串長度
用戶輸入做爲格式化字符串的,檢查關鍵字「%」
用戶輸入做爲業務數據,對關鍵字進行檢查、轉義
13.1 字符串操做安全
規則13.1 確保全部字符串是以NULL結束。
說明:C語言中‟\0‟做爲字符串的結束符,即NULL結束符。標準字符串處理函數(如strcpy()、strlen())依賴NULL結束符來肯定字符串的長度。沒有正確使用NULL結束字符串會致使緩衝區溢出和其它未定義的行爲。
爲了不緩衝區溢出,經常會用相對安全的限制字符數量的字符串操做函數代替一些危險函數。如:
用strncpy()代替strcpy()
用strncat()代替strcat()
用snprintf()代替sprintf()
用fgets()代替gets()
這些函數會截斷超出指定限制的字符串,可是要注意它們並不能保證目標字符串老是以NULL結尾。若是源字符串的前n個字符中不存在NULL字符,目標字符串就不是以NULL結尾。
示例:
char a[16];
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第52頁,共61頁Page 52 , Total61
strncpy(a, "0123456789abcdef", sizeof(a));
上述代碼存在安全風險:在調用strncpy()後,字符數組a中的字符串是沒有NULL結束符的,也沒有空間存放NULL結束符。
正確寫法:截斷字符串,保證字符串以NULL結束。
char a[16];
strncpy(a, "0123456789abcdef", sizeof(a) - 1 );
a[sizeof(a) - 1] = '\0';
規則13.2 不要將邊界不明確的字符串寫到固定長度的數組中。
說明:邊界不明確的字符串(如來自gets()、getenv()、scanf()的字符串),長度可能大於目標數組長度,直接拷貝到固定長度的數組中容易致使緩衝區溢出。
示例:
char buff[256];
char *editor = getenv("EDITOR");
if (editor != NULL)
{
strcpy(buff, editor);
}
上述代碼讀取環境變量"EDITOR"的值,若是成功則拷貝到緩衝區buff中。而從環境變量獲取到的字符串長度是不肯定的,把它們拷貝到固定長度的數組中極可能致使緩衝區溢出。
正確寫法:計算字符串的實際長度,使用malloc分配指定長度的內存
char *buff;
char *editor = getenv("EDITOR");
if (editor != NULL)
{
buff = malloc(strlen(editor) + 1);
if (buff != NULL)
{
strcpy(buff, editor);
}
}
13.2 整數安全
C99標準定義了整型提高(integer promotions)、整型轉換級別(integer conversion rank)以及普通算術轉換(usual arithmetic conversions)的整型操做。不過這些操做實際上也帶來了安全風險。
規則13.3 避免整數溢出。
說明:當一個整數被增長超過其最大值時會發生整數上溢,被減少小於其最小值時會發生整數下溢。帶符號和無符號的數都有可能發生溢出。
示例1:有符號和無符號整數的上溢和下溢
int i;
unsigned int j;
i = INT_MAX; // 2,147,483,647
i++;
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第53頁,共61頁Page 53 , Total61
printf("i = %d\n", i); // i=-2,147,483,648
j = UINT_MAX; // 4,294,967,295;
j++;
printf("j = %u\n", j); // j = 0
i = INT_MIN; // -2,147,483,648;
i--;
printf("i = %d\n", i); // i = 2,147,483,647
j = 0;
j--;
printf("j = %u\n", j); // j = 4,294,967,295
示例2:整數下溢致使報文長度異常
/* 報文長度減去FSM頭的長度*/
unsigned int length;
length -= FSM_HDRLEN ;
處理太短報文時,length的長度可能小於FSM_HDRLEN,減法的結果小於。因爲length是無符號數,結果返回了一個很大的數。
正確寫法:增長長度檢查
if (length < FSM_HDRLEN )
{
return VOS_ERROR;
}
length -= FSM_HDRLEN ;
規則13.4 避免符號錯誤。
說明:有時從帶符號整型轉換到無符號整型會發生符號錯誤,符號錯誤並不丟失數據,但數據失去了原來的含義。
帶符號整型轉換到無符號整型,最高位(high-order bit)會喪失其做爲符號位的功能。若是該帶符號整數的值非負,那麼轉換後值不變;若是該帶符號整數的值爲負,那麼轉換後的結果一般是一個很是大的正數。
示例:符號錯誤繞過長度檢查
#define BUF_SIZE 10
int main(int argc,char* argv[])
{
int length;
char buf[BUF_SIZE];
if (argc != 3)
{
return -1;
}
length = atoi(argv[1]); //若是atoi返回的長度爲負數
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第54頁,共61頁Page 54 , Total61
if (length < BUF_SIZE) // len爲負數,長度檢查無效
{
memcpy(buf, argv[2], length); /* 帶符號的len被轉換爲size_t類型的無符號整數,負值被解釋爲一個極大的正整數。memcpy()調用時引起buf緩衝區溢出 */
printf("Data copied\n");
}
else
{
printf("Too many data\n");
}
}
正確寫法1:將len聲明爲無符號整型
#define BUF_SIZE 10
int main(int argc, char* argv[])
{
unsigned int length;
char buf[BUF_SIZE];
if (argc != 3)
{
return -1;
}
length = atoi(argv[1]);
if (length < BUF_SIZE)
{
memcpy(buf, argv[2], length);
printf("Data copied\n");
}
else
{
printf("Too much data\n");
}
return 0;
}
正確寫法2:增長對len的更有效的範圍校驗
#define BUF_SIZE 10
int main(int argc, char* argv[])
{
int length;
char buf[BUF_SIZE];
if (argc != 3)
{
return -1;
}
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第55頁,共61頁Page 55 , Total61
length = atoi(argv[1]);
if ((length > 0) && (length < BUF_SIZE))
{
memcpy(buf, argv[2], length);
printf("Data copied\n");
}
else
{
printf("Too much data\n");
}
return 0;
}
規則13.5:避免截斷錯誤。
說明:將一個較大整型轉換爲較小整型,而且該數的原值超出較小類型的表示範圍,就會發生截斷錯誤,原值的低位被保留而高位被丟棄。截斷錯誤會引發數據丟失。
使用截斷後的變量進行內存操做,極可能會引起問題。
示例:
int main(int argc, char* argv[])
{
unsigned short total = strlen(argv[1]) + strlen(argv[2]) + 1;
char* buffer = (char*)malloc(total);
strcpy(buffer, argv[1]);
strcat(buffer, argv[2]);
free(buffer);
return 0;
}
示例代碼中total被定義爲unsigned short,相對於strlen()的返回值類型size_t(一般爲unsigned long)過小。若是攻擊者提供的兩個入參長度分別爲65500和36,unsigned long的65500+36+1會被取模截斷,total的最終值是(65500+36+1)%65536 = 1。malloc()只爲buff分配了1字節空間,爲strcpy()和strcat()的調用創造了緩衝區溢出的條件。
正確寫法:將涉及到計算的變量聲明爲統一的類型,並檢查計算結果。
int main(int argc, char* argv[])
{
size_t total = strlen(argv[1]) + strlen(argv[2]) + 1;
if ((total <= strlen(argv[1])) || (total <= strlen(argv[2])))
{
/* handle error */
return -1;
}
char* buffer = (char*)malloc(total);
strcpy(buffer, argv[1]);
strcat(buffer, argv[2]);
free(buffer);
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第56頁,共61頁Page 56 , Total61
return 0;
}
13.3 格式化輸出安全
規則13.6:確保格式字符和參數匹配。
說明:使用格式化字符串應該當心,確保格式字符和參數之間的匹配,保留數量和數據類型。格式字符和參數之間的不匹配會致使未定義的行爲。大多數狀況下,不正確的格式化字符串會致使程序異常終止。
示例:
char *error_msg = "Resource not available to user.";
int error_type = 3;
/* 格式字符和參數的類型不匹配*/
printf("Error (type %s): %d\n", error_type, error_msg);
/* 格式字符和參數的數量不匹配*/
printf("Error: %s\n");
格式化字符串在編碼時會大量使用,容易copy-paste省事,這就容易出現不匹配的錯誤。
規則13.7 避免將用戶輸入做爲格式化字符串的一部分或者所有。
說明:調用格式化I/O函數時,不要直接或者間接將用戶輸入做爲格式化字符串的一部分或者所有。攻擊者對一個格式化字符串擁有部分或徹底控制,存在如下風險:進程崩潰、查看棧的內容、改寫內存、甚至執行任意代碼。
示例1:
char input[1000];
if (fgets(input, sizeof(input) - 1, stdin) == NULL)
{
/* handle error */
}
input[sizeof(input)-1] = '\0';
printf(input);
上述代碼input直接來自用戶輸入,並做爲格式化字符串直接傳遞給printf()。當用戶輸入的是「%s%s%s%s%s%s%s%s%s%s%s%s」,就可能觸發無效指針或未映射的地址讀取。格式字符%s顯示棧上相應參數所指定的地址的內存。這裏input被當成格式化字符串,而沒有提供參數,所以printf()讀取棧中任意內存位置,指導格式字符耗盡或者遇到一個無效指針或未映射地址爲止。
正確作法:給printf()傳兩個參數,第一個參數爲」%s」,目的是將格式化字符串肯定下來;第二個參數爲用戶輸入input。
char input[1000];
if (fgets(input, sizeof(input)-1, stdin) == NULL)
{
/* handle error */
}
input[sizeof(input)-1] = '\0';
printf(「%s」, input);
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第57頁,共61頁Page 57 , Total61
示例2:
void check_password(char *user, char *password)
{
if (strcmp(password(user), password) != 0)
{
char *msg = malloc(strlen(user) + 100);
if (!msg)
{
/* handle error condition */
}
sprintf(msg, "%s login incorrect", user);
fprintf(STDERR, msg);
syslog(LOG_INFO, msg);
free(msg);
}
/*…*/
}
上述代碼檢查給定用戶名及其口令是否匹配,當不匹配時顯示一條錯誤信息,並將錯誤信息寫入日誌中。一樣的,若是user爲」 %s%s%s%s%s%s%s%s%s%s%s%s」,通過格式化函數sprintf()的拼裝後,msg指向的字符串爲」 %s%s%s%s%s%s%s%s%s%s%s%s login incorrect」,在fprintf()調用中,msg將做爲fprintf()的格式化字符串,可能引起如同示例1同樣的問題。並且,syslog()函數也同樣存在格式化字符串的問題。
正確作法:格式化字符串由代碼肯定,未經檢查過濾的用戶輸入只能做爲參數。
void check_password(char *user, char *password)
{
if (strcmp(password(user), password) != 0)
{
char *msg = malloc(strlen(user) + 100);
if (!msg)
{
/* handle error condition */
}
sprintf(msg, "%s password incorrect", user);
fprintf(stderr, "%s", user);
syslog(LOG_INFO, "%s", msg);
free(msg);
}
}
13.4 文件I/O安全
規則13.8 避免使用strlen()計算二進制數據的長度。
說明:strlen()函數用於計算字符串的長度,它返回字符串中第一個NULL結束符以前的字符的數量。所以用strlen()處理文件I/O函數讀取的內容時要當心,由於這些內容多是二進制也多是文本。
示例:
char buf[BUF_SIZE + 1];
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第58頁,共61頁Page 58 , Total61
if (fgets(buf, sizeof(buf), fp) == NULL)
{
/* handle error */
}
buf[strlen(buf) - 1] = '\0';
上述代碼試圖從一個輸入行中刪除行尾的換行符(\n)。若是buf的第一個字符是NULL,strlen(buf)返回0,這時對buf進行數組下標爲[-1]的訪問操做將會越界。
正確作法:在不能肯定從文件讀取到的數據的類型時,不要使用依賴NULL結束符的字符串操做函數。
char buf[BUF_SIZE + 1];
char *p;
if (fgets(buf, sizeof(buf), fp))
{
p = strchr(buf, '\n');
if (p)
{
*p = '\0';
}
}
else
{
/* handle error condition */
}
規則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的值)執行比較。
示例:
char buf[BUF_SIZE];
char ch;
int i = 0;
while ( (ch = getchar()) != '\n' && ch != EOF )
{
if ( i < BUF_SIZE - 1 )
{
buf[i++] = ch;
}
}
buf[i] = '\0'; /* terminate NTBS */
正確作法:使用int類型的變量接受getchar()的返回值。
char buf[BUF_SIZE];
int ch;
int i = 0;
while (((ch = getchar()) != '\n') && ch != EOF)
{
if (i < BUF_SIZE - 1)
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第59頁,共61頁Page 59 , Total61
{
buf[i++] = ch;
}
}
buf[i] = '\0'; /* terminate NTBS */
對於sizeof(int) == sizeof(char)的平臺,用int接收返回值也可能沒法與EOF區分,這時要用feof()和ferror()檢測文件尾和文件錯誤。
13.5 其它
規則13.10 防止命令注入。
說明:C99函數system()經過調用一個系統定義的命令解析器(如UNIX的shell,Windows的CMD.exe)來執行一個指定的程序/命令。相似的還有POSIX的函數popen()。
若是system()的參數由用戶的輸入組成,惡意用戶能夠經過構造惡意輸入,改變system()調用的行爲。
示例:
system(sprintf("any_exe %s", input));
若是惡意用戶輸入參數:
happy; useradd attacker
最終shell將字符串「any_exe happy; useradd attacker」解釋爲兩條獨立的命令:
正確作法:使用POSIX函數execve()代替system().
void secuExec (char *input)
{
pid_t pid;
char *const args[] = {"", input, NULL};
char *const envs[] = {NULL};
pid = fork();
if (pid == -1)
{
puts("fork error");
}
else if (pid == 0)
{
if (execve("/usr/bin/any_exe", args, envs) == -1)
{
puts("Error executing any_exe");
}
}
return;
}
Windows環境可能對execve()的支持不是很完善,建議使用Win32 API CreateProcess()代替system()。
14 單元測試
規則14.1 在編寫代碼的同時,或者編寫代碼前,編寫單元測試用例驗證軟件設計/編碼的正確。
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第60頁,共61頁Page 60 , Total61
建議14.1 單元測試關注單元的行爲而不是實現,避免針對函數的測試。
說明:應該將被測單元看作一個被測的總體,根據實際資源、進度和質量風險,權衡代碼覆蓋、打樁工做量、補充測試用例的難度、被測對象的穩定程度等,通常狀況下建議關注模塊/組件的測試,儘可能避免針對函數的測試。儘管有時候單個用例只能專一於對某個具體函數的測試,但咱們關注的應該是函數的行爲而不是其具體實現細節。
15 可移植性
規則15.1 不能定義、重定義或取消定義標準庫/平臺中保留的標識符、宏和函數。
建議15.1 不使用與硬件或操做系統關係很大的語句,而使用建議的標準語句,以提升軟件的可移植性和可重用性。
說明:使用標準的數據類型,有利於程序的移植。
示例:以下例子(在DOS下BC3.1環境中),在移植時可能產生問題。
void main()
{
register int index; // 寄存器變量
_AX = 0x4000; // _AX是BC3.1提供的寄存器「僞變量」
... // program code
}
建議15.2 除非爲了知足特殊需求,避免使用嵌入式彙編。
說明:程序中嵌入式彙編,通常都對可移植性有較大的影響。
16 業界編程規範
本次編程規範整理的原則是求精不求全,主要針對華爲當前編碼上的突出問題,因此在全面性上難免有所欠缺。業界一些公司、組織也發佈了一些編程規範,對編程語言的缺陷、使用風險都有很好的描述,這裏作一些簡單的推介,有興趣的同窗能夠在平時學習中能夠參考,提升本身的編程能力。
google C++編程指南
目標:
加強代碼一致性,建立通用的、必需的習慣用語和模式能夠使代碼更加容易理解
C++是一門包含大量高級特性的巨型語言,某些狀況下,咱們會限制甚至禁止使用某些特性使代碼簡化,避免可能致使的各類問題
包含的內容:頭文件、命名規則、註釋、語言特性的使用規則、編碼格式
特色:強調理解基礎上的遵循,一個規則一般明確說明其優勢、缺點,並舉不少例子,讓讀者在理解的基礎上遵循,不像規章制度那樣生硬和抽象,實際上讀起來更像一個教程。好比:禁止使用C++異常,花了一頁紙的篇幅來解釋使用和不使用的優缺點,很是容易理解
推薦語:讀起來很是舒服,拋開編程規範,拿來做爲理解學習C++也是不錯的
推薦度:★★★★★
密級:confidentiality level
DKBA 2826-2011.5
2011-06-02 華爲機密,未經許可不得擴散 Huawei Confidential 第61頁,共61頁Page 61 , Total61
汽車業C語言使用規範(MISRA)
目標:由於編譯器、編程人員理解、C語言本等緣由,徹底放開使用C語言存在一些風險,所以制定這個規範的目標爲了促進C語言的最爲安全的使用而定義一些規則。
特色:規則都是針對的是C語言自己缺陷或容易被誤解的點,如:自動變量若是不初始化就使用會出現隨機值、不一樣類型數據賦值容易出現的隱式轉換;沒有包含諸如註釋、變量名、編碼格式等統一編程風格的內容。
推薦語:對C的缺點了如指掌,能夠幫助更好的掌握C語言,避免編程陷阱,提升程序可靠性。
推薦度:★★★★php