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 代碼總體原則
1、清晰第一
清晰性是易於維護、易於重構的程序必需具備的特征。代碼首先是給人讀的,好的代碼應當可以像文章一樣發聲朗誦出來。
目前軟件維護期成本占整個生命周期成本的40%~90%。根據業界經驗,維護期變更代碼的成本,小型系統是開發期的5倍,大型系統(100萬行代碼以上)可以達到100倍。業界的調查指出,開發組平均大約一半的人力用於彌補過去的錯誤,而不是添加新的功能來幫助公司提高競爭力。
“程序必須為閱讀它的人而編寫,只是順便用於機器執行。”——Harold Abelson 和 Gerald Jay Sussman
“編寫程序應該以人為本,計算機第二。”——Steve McConnell
本規范通過后文中的原則(如頭優秀的代碼可以自我解釋,不通過注釋即可輕易讀懂/頭文件中適合放置接口的聲明,不適合放置實現/除了常見的通用縮寫以外,不使用單詞縮寫,不得使用漢語拼音)、規則(如防止局部變量與全局變量同名)等說明清晰的重要性。
一般情況下,代碼的可閱讀性高於性能,只有確定性能是瓶頸時,才應該主動優化。
2、簡潔為美
簡潔就是易於理解並且易於實現。代碼越長越難以看懂,也就越容易在修改時引入錯誤。寫的代碼越多,意味着出錯的地方越多,也就意味着代碼的可靠性越低。因此,我們提倡大家通過編寫簡潔明了的代碼來提升代碼可靠性。
廢棄的代碼(沒有被調用的函數和全局變量)要及時清除,重復代碼應該盡可能提煉成函數。
本規范通過后文中的原則(如文件應當職責單一/一個函數僅完成一件功能)、規則(重復代碼應該盡可能提煉成函數/避免函數過長,新增函數不超過50行)等說明簡潔的重要性。
3、選擇合適的風格,與代碼原有風格保持一致
產品所有人共同分享同一種風格所帶來的好處,遠遠超出為了統一而付出的代價。在公司已有編碼規范的指導下,審慎地編排代碼以使代碼盡可能清晰,是一項非常重要的技能。如果重構/修改其他風格的代碼時,比較明智的做法是根據現有代碼的現有風格繼續編寫代碼,或者使用格式轉換工具進行轉
密級: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 要時刻注意易混淆的操作符。
說明:包括易混淆和的易用錯操作符
1、易混淆的操作符
C語言中有些操作符很容易混淆,編碼時要非常小心。
賦值操作符“=” 邏輯操作符“==”
關系操作符“<” 位操作符"<<"
關系操作符“>” 位操作符“>>”
邏輯操作符“||” 位操作符"|"
邏輯操作符“&&” 位操作符"&"
邏輯操作符"!" 位操作符“~”
2、易用錯的操作符
(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;
函數頭注釋建議寫到聲明處。並非所有函數都必須寫注釋,建議針對這樣的函數寫注釋:重要的、復雜的函數,提供外部使用的接口函數。
延伸閱讀材料:
1、《代碼大全第2版》(Steve McConnell 著 金戈/湯凌/陳碩/張菲 譯 電子工業出版社 2006年3月)"第32章自說明代碼"。
2、《代碼整潔之道》(Robert C.Martin 著 韓磊 譯 人民郵電出版社2010年1月)第四章"注釋"。
3、《敏捷軟件開發:原則、模式與實踐》(Robert C.Martin 著 鄧輝 譯 清華大學出版社2003年9月)"第5章重構"。
4、《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 表達式的值在標准所允許的任何運算次序下都應該是相同的。
說明:除了少數操作符(函數調用操作符 ( )、&&、| |、? : 和 , (逗號)) 之外,子表達式所依據的運算次序是未指定的並會隨時更改。注意,運算次序的問題不能使用括號來解決,因為這不是優先級的問題。
將復合表達式分開寫成若干個簡單表達式,明確表達式的運算次序,就可以有效消除非預期副作用。
1、自增或自減操作符
示例:
x = b[i] + i++;
b[i] 的運算是先於還是后於 i ++ 的運算,表達式會產生不同的結果,把自增運算做為單獨的語句,可以避免這個問題。
x = b[i] + i;
i ++;
2﹑函數參數
說明:函數參數通常從右到左壓棧,但函數參數的計算次序不一定與壓棧次序相同。
示例:
x = func( i++, i);
應該修改代碼明確先計算第一個參數:
i++;
x = func(i, i);
3、函數指針
說明:函數參數和函數自身地址的計算次序未定義。
示例:
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();
5、嵌套賦值語句
說明:表達式中嵌套的賦值可以產生附加的副作用。不給這種能導致對運算次序的依賴提供任何機會的最好做法是,不要在表達式中嵌套賦值語句。
示例:
x = y = y = z / 3;
x = y = y++;
6、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語言,避免編程陷阱,提高程序可靠性。
推薦度:★★★★