編程規范
?
今天人們越來越明白軟件設計更多地是一種工程而不是一種個人藝術由于大型產品的開
發通常由很多的人協同作戰如果不統一編程規范最終合到一起的程序其可讀性將較
差這不僅給代碼的理解帶來障礙增加維護階段的工作量同時不規范的代碼隱含錯誤的
可能性也比較大
BELL實驗室的研究資料表明軟件錯誤中18%左右產生于概要設計階段15%左右產生于詳細
設計階段而編碼階段產生的錯誤占的比例則接近50%分析表明編碼階段產生的錯誤當
中語法錯誤大概占20%左右而由于未嚴格檢查軟件邏輯導致的錯誤函數模塊之間
接口錯誤及由于代碼可理解度低導致優化維護階段對代碼的錯誤修改引起的錯誤則占了一半
以上
可見提高軟件質量必須降低編碼階段的錯誤率如何有效降低編碼階段的錯誤呢BELL實
驗室的研究人員制定了詳細的軟件編程規范并培訓每一位程序員最終的結果把編碼階段
的錯誤降至10%左右同時也降低了程序的測試費用效果相當顯著
本文從代碼的可維護性可讀可理解性可修改性 代碼邏輯與效率函數模塊接
口可測試性四個方面闡述了軟件編程規范規范分成規則和建議兩種其中規則部分為強
制執行項目而建議部分則不作強制可根據習慣取舍
2. 編碼規范
2.1. 排版風格
<規則 1> 程序塊采用縮進風格編寫縮進為4個空格位排版不混合使用空格和TAB鍵
<規則2> 在兩個以上的關鍵字變量常量進行對等操作時它們之間的操作符之前之后
或者前后要加空格進行非對等操作時如果是關系密切的立即操作符如> 后不應
加空格
采用這種松散方式編寫代碼的目的是使代碼更加清晰例如
(1) 逗號分號只在后面加空格
printf("%d %d %d" , a, b, c);
(2)比較操作符, 賦值操作符"=" "+="算術操作符"+""%"邏輯操作符"&&""&"位
域操作符"<<""^"等雙目操作符的前后加空格
if(lCurrentTime >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;
(3)"!""~""++""--""&"地址運算符等單目操作符前后不加空格
*pApple = 'a'; // 內容操作"*"與內容之間
flag = !bIsEmpty; // 非操作"!"與內容之間
p = &cMem; // 地址操作"&" 與內容之間
i++; // "++","--"與內容之間
(4)"->""."前后不加空格
p->id = pId; // "->"指針前后不加空格
由于留空格所產生的清晰性是相對的所以在已經非常清晰的語句中沒有必要再留空格
如最內層的括號內側(即左括號后面和右括號前面)不要加空格因為在C/C++語言中括號已
經是最清晰的標志了
另外在長語句中如果需要加的空格非常多那么應該保持整體清晰而在局部不加空
格
最后即使留空格也不要連續留兩個以上空格(為了保證縮進和排比留空除外)
<規則3> 函數體的開始類的定義結構的定義iffordowhileswitch及case語句
中的程序都應采用縮進方式憑捄蛻}捰稟獨占一行并且位于同一列同時與引用它們的語
句左對齊
例如下例不符合規范
for ( ... ) {
... // 程序代碼
}
if ( ... )
{
... // 程序代碼
}
void DoExam( void )
{
... // 程序代碼
}
應如下書寫
for ( ... )
{
... // 程序代碼
}
if ( ... )
{
... // 程序代碼
}
void DoExam( void )
{
... // 程序代碼
}
<規則4> 功能相對獨立的程序塊之間或forifdowhileswitch等語句前后應加一空
行
例如以下例子不符合規范
例一
if ( ! ValidNi( ni ) )
{
... // 程序代碼
}
nRepssnInd = SsnData[ index ].nRepssnIndex ;
nRepssnNi = SsnData[ index ].ni ;
例二
char *pContext;
int nIndex;
long lCounter;
pContext = new (CString);
if(pContext == NULL)
{
return FALSE;
}
應如下書寫
例一
if ( ! ValidNi( ni ) )
{
... // 程序代碼
}
nRepssnInd = SsnData[ index ].nRepssnIndex ;
nRepssnNi = SsnData[ index ].ni ;
例二
char *pContext;
int nIndex;
long lCounter;
pContext = new (CString);
if(pContext == NULL)
{
return FALSE;
}
<規則5> ifwhileforcasedefaultdo等語句自占一行
示例如下例子不符合規范
if(pUserCR == NULL) return;
應如下書寫
if( pUserCR == NULL )
{
return;
}
<規則6> 若語句較長多于80字符 可分成多行寫劃分出的新行要進行適應的縮進使
排版整齊語句可讀
memset(pData->pData + pData->nCount, 0,
(m_nMax - pData->nCount) * sizeof(LPVOID));
CNoTrackObject* pValue =
(CNoTrackObject*)_afxThreadData->GetThreadValue(m_nSlot)
;
for ( i = 0, j = 0 ; ( i < BufferKeyword[ WordIndex ].nWordLength )
&& ( j < NewKeyword.nWordLength ) ; i ++ , j ++ )
{
... // 程序代碼
}
<規則7> 一行最多寫一條語句
示例如下例子不符合規范
rect.length = 0 ; rect.width = 0 ;
rect.length = width = 0;
都應書寫成
rect.length = 0 ;
rect.width = 0 ;
<規則8> 對結構成員賦值等號對齊
示例
rect.top = 0;
rect.left = 0;
rect.right = 300;
rect.bottom = 200;
<規則9> #define的各個字段對齊
以下示例不符合規范
#define MAX_TASK_NUMBER 100
#define LEFT_X 10
#define BOTTOM_Y 400
應書寫成
#define MAX_TASK_NUMBER 100
#define LEFT_X 10
#define BOTTOM_Y 400
<規則10> 不同類型的操作符混合使用時使用括號給出優先級
如本來是正確的代碼
if( year % 4 == 0 || year % 100 != 0 && year % 400 == 0 )
如果加上括號則更清晰
if((year % 4) == 0 || ((year % 100) != 0 && (year % 400) == 0))
2.2. 可理解性
2.2.1.注釋
注釋的原則是有助于對程序的閱讀理解注釋不宜太多也不能太少太少不利于代碼理解
太多則會對閱讀產生干擾因此只在必要的地方才加注釋而且注釋要準確易懂盡可能
簡潔注釋量一般控制在30%到50%之間
<規則1> 程序在必要的地方必須有注釋注釋要準確易懂簡潔
例如如下注釋意義不大
/* 如果bReceiveFlag 為 TRUE */
if ( bReceiveFlag == TRUE)
而如下的注釋則給出了額外有用的信息
/* 如果mtp 從連接處獲得一個消息*/
if ( bReceiveFlag == TURE)
<規則2> 注釋應與其描述的代碼相近對代碼的注釋應放在其上方或右方對單條語句的注
釋相鄰位置不可放在下面如放于上方則需與其上面的代碼用空行隔開
示例如下例子不符合規范
例子1
/* 獲得系統指針和網絡指針的副本 */
nRepssnInd = SsnData[ index ].nRepssnIndex ;
nRepssnNi = SsnData[ index ].ni ;
例子2
nRepssnInd = SsnData[ index ].nRepssnIndex ;
nRepssnNi = SsnData[ index ].ni ;
/*獲得系統指針和網絡指針的副本 */
應如下書寫
/*獲得系統指針和網絡指針的副本 */
nRepssnInd = SsnData[ index ].nRepssnIndex ;
nRepssnNi = SsnData[ index ].ni ;
<規則3> 對于所有的常量變量數據結構聲明(包括數組結構類枚舉等)如果其命
名不是充分自注釋的在聲明時都必須加以注釋說明其含義
示例
/* 活動任務的數量 */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /*活動任務的數量 */
/* 帶原始用戶信息的SCCP接口 */
enum SCCP_USER_PRIMITIVE
{
N_UNITDATA_IND , /* 向SCCP用戶報告單元數據已經到達 */
N_UNITDATA_REQ , /* SCCP用戶的單元數據發送請求 */
}
<規則4> 頭文件源文件的頭部應進行注釋注釋必須列出文件名作者目的功
能修改日志等
例如
/*********************************************
文件名
編寫者
編寫日期
簡要描述
修改記錄
********************************************/
說明摷蛞 枋鰯一項描述本文件的目的和功能等撔薷募鍬紨是修改日志列表每條修改
記錄應包括修改日期修改者及修改內容簡述
<規則5> 函數頭部應進行注釋列出函數的目的功能輸入參數輸出參數修改日志
等
形式如下
/*************************************************
函數名稱
簡要描述 // 函數目的功能等的描述
輸入 // 輸入參數說明包括每個參數的作用取值說明及參數間關
系
輸出 // 輸出參數的說明返回值的說明
修改日志
*************************************************/
對一些復雜的函數在注釋中最好提供典型用法
<規則6> 仔細定義并明確公共變量的含義作用取值范圍及使用方法
在對變量聲明的同時應對其含義作用取值范圍及使用方法進行注釋說明同時若有必
要還應說明與其它變量的關系明確公共變量與操作此公共變量的函數或過程的關系如訪
問修改及創建等
示例
/* SCCP轉換時錯誤代碼 */
/* 全局錯誤代碼含義如下 */ // 變量作用含義
/* 0 成功 1 GT 表錯誤 2 GT 錯誤 其它值未使用 */ // 變量
取值范圍
<規則7> 對指針進行充分的注釋說明對其作用含義使用范圍注意事項等說明清楚
在對指針變量特別是比較復雜的指針變量聲明時應對其含義作用及使用范圍進行注釋
說明如有必要還應說明其使用方法注意事項等
示例
/* 學生記錄列表的頭指針 */
/* 當在此模塊中創建該列表時該頭指針必須初始化 */
/* 這樣可以利用GetListHead()獲得這一列表*/ //指針作用含義
/* 該指針只在本模塊使用其它模塊通過調用GetListHead()獲取*/
/* 當使用時必須保證它非空 */ //使用范圍方法
STUDENT_RECORD *pStudentRecHead;
<規則8> 對重要代碼段的功能意圖進行注釋提供有用的額外的信息并在該代碼段的
結束處加一行注釋表示該段代碼結束
示例
/* 可選通道的組合 */
if ((gsmBCIe31->radioChReq >= DUAL_HR_RCR)
&& (gsmBCIe32->radioChReq >= DUAL_HR_RCR))
{
gsmBCIe31->radioChReq = FR_RCR;
gsmBCIe32->radioChReq = FR_RCR;
}
else if ((gsmBCIe31->radioChReq >= DUAL_HR_RCR)
&& (gsmBCIe32->radioChReq == FR_RCR) )
{
gsmBCIe31->radioChReq = FR_RCR;
}
else if ((gsmBCIe31->radioChReq == FR_RCR)
&& (gsmBCIe32->radioChReq >= DUAL_HR_RCR))
{
gsmBCIe32->radioChReq = FR_RCR;
}
/* 本塊結束 ( 可選通道組合 ) */
<規則9> 在switch語句中對沒有break語句的case分支加上注釋說明
示例
switch(SubT30State)
{
case TA0:
AT(CHANNEL, "AT+FCLASS=1\r", 0);
if(T30Status != 0)
{
return(1);
}
InitFax(); /* 準備發送傳真 */
AT(CHANNEL, "ATD\r",-1); /*發送CNG 接收 CED 和 HDLC 標志*/
T1_Flg = 1;
iResCode = 0;
/* 沒有 break; */
case TA1:
iResCode = GetModemMsg(CHANNEL);
break;
default:
break;
}
<規則 10> 維護代碼時要更新相應的注釋刪除不再有用的注釋
保持代碼注釋的一致性避免產生誤解
2.2 命名
本文列出Visual C++的標識符命名規范
<規則 1> 標識符縮寫
形成縮寫的幾種技術
1) 去掉所有的不在詞頭的元音字母如screen寫成scrn, primtive寫成prmv
2) 使用每個單詞的頭一個或幾個字母如Channel Activation寫成ChanActiv
Release Indication寫成RelInd
3) 使用變量名中每個有典型意義的單詞如Count of Failure寫成FailCnt
4) 去掉無用的單詞后綴 ing, ed等如Paging Request寫成PagReq
5) 使用標準的或慣用的縮寫形式包括協議文件中出現的縮寫形式如BSIC(Base
Station Identification Code)MAP(Mobile Application Part)
關于縮寫的準則
1) 縮寫應該保持一致性如Channel不要有時縮寫成Chan有時縮寫成ChLength有時
縮寫成Len有時縮寫成len
2) 在源代碼頭部加入注解來說明協議相關的非通用縮寫
3) 標識符的長度不超過32個字符
<規則2> 變量命名約定
參照匈牙利記法即
[作用范圍域前綴] + [前綴] + 基本類型 + 變量名
其中
前綴是可選項以小寫字母表示
基本類型是必選項以小寫字母表示
變量名是必選項可多個單詞(或縮寫)合在一起每個單詞首字母大寫
前綴列表如下
前綴 意義 舉例
g_ Global 全局變量 g_MyVar
m_ 類成員變量或模塊級變量 m_ListBox, m_Size
s_ static 靜態變量 s_Count
h Handle 句柄 hWnd
p Pointer 指針 pTheWord
lp Long Point 長指針 lpCmd
a Array 數組 aErr
基本類型列表如下
基本類型 意義 舉例
b Boolean 布爾 bIsOK
by Byte 字節 byNum
c Char 字符 cMyChar
i或n Intger 整數 nTestNumber
u Unsigned integer 無符號整數 uCount
ul Unsigned Long 無符號長整數 ulTime
w Word 字 wPara
dw Double Word 雙字 dwPara
l Long 長型 lPara
f Float 浮點數 fTotal
s String 字符串 sTemp
sz NULL結束的字符串 szTrees
fn Funtion 函數 fnAdd
enm 枚舉型 enmDays
x,y x,y坐標
<規則3> 宏和常量的命名
宏和常量的命名規則單詞的字母全部大寫各單詞之間用下劃線隔開命名舉例
#define MAX_SLOT_NUM 8
#define EI_ENCR_INFO 0x07
const int MAX_ARRAY
<規則4> 結構和結構成員的命名
結構名各單詞的字母均為大寫單詞間用下劃線連接可用或不用typedef但是要保持一
致不能有的結構用typedef有的又不用如
typedef struct LOCAL_SPC_TABLE_STRU
{
char cValid;
int nSpcCode[MAX_NET_NUM];
} LOCAL_SPC_TABLE ;
結構成員的命名同變量的命名規則
<規則5> 枚舉和枚舉成員的命名
枚舉名各單詞的字母均為大寫單詞間用下劃線隔開
枚舉成員的命名規則單詞的字母全部大寫各單詞之間用下劃線隔開要求各成員的第一
個單詞相同命名舉例
typdef enum
{
LAPD_ MDL_ASSIGN_REQ,
LAPD_MDL_ASSIGN_IND,
LAPD_DL_DATA_REQ,
LAPD_DL_DATA_IND,
LAPD_DL_UNIT_DATA_REQ,
LAPD_DL_UNIT_DATA_IND,
} LAPD_PRMV_TYPE;
<規則6> 類的命名
前綴 意義 舉例
C 類 CMyClass
CO COM類 COMMyObjectClass
CF COM class factory CFMyClassFactory
I COM interface class IMyInterface
CImpl COM implementation class CImplMyInterface
<規則7> 函數的命名
單詞首字母為大寫其余均為小寫單詞之間不用下劃線函數名應以一個動詞開頭即函
數名應類似摱 黿峁箶命名舉例
void PerformSelfTest(void) ;
void ProcChanAct(MSG_CHAN_ACTIV *pMsg, UC MsgLen);
2.3. 可維護性
<規則1> 在邏輯表達式中使用明確的邏輯判斷
示例如下邏輯表達式不規范
1) if ( strlen(strName) )
2) for ( index = MAX_SSN_NUMBER; index ; index -- )
3) while ( p && *p ) // 假設p為字符指針
應改為如下
1) if ( strlen(strName) != 0 )
2) for ( index = MAX_SSN_NUMBER; index != 0 ; index -- )
3) while ((p != NULL) && (*p != '\0' ))
<規則2> 預編譯條件不應分離一完整的語句
不正確
if (( cond == GLRUN)
#ifdef DEBUG
|| (cond == GLWAIT)
#endif
)
{
}
正確
#ifdef DEBUG
if( cond == GLRUN || cond == GLWAIT )
#else
if( cond == GLRUN )
#endif
{
}
<規則3> 在宏定義中合并預編譯條件
不正確
#ifdef EXPORT
for ( i = 0; i < MAX_MSXRSM; i++ )
#else
for ( i = 0; i < MAX_MSRSM; i++ )
#endif
正確
頭文件中
#ifdef EXPORT
#define MAX_MS_RSM MAX_MSXRSM
#else
#define MAX_MS_RSM MAX_MSRSM
#endif
源文件中
for( i = 0; i < MAX_MS_RSM; i++ )
<規則4> 使用宏定義表達式時要使用完備的括號
如下的宏定義表達式都存在一定的隱患
#define REC_AREA(a, b) a * b
#define REC_AREA(a, b) (a * b)
#define REC_AREA(a, b) (a) * (b)
正確的定義為
#define REC_AREA(a, b) ((a) * (b))
<規則5> 宏所定義的多條表達式應放在大括號內
示例下面的語句只有宏中的第一條表達式被執行為了說明問題for語句的書寫稍不符
規范
#define INIT_RECT_VALUE( a, b ) a = 0 ; b = 0 ;
for ( index = 0 ; index < RECT_TOTAL_NUM ; index ++ )
INIT_RECT_VALUE( rect.a, rect.b ) ;
正確的用法應為
#define INIT_RECT_VALUE( a, b ) { a =
0 ; b = 0 ; }
for ( index = 0 ; index < RECT_TOTAL_NUM ; index ++ )
{
INIT_RECT_VALUE( rect[ index ].a, rect[ index ].b ) ;
}
<規則6> 宏定義不能隱藏重要的細節避免有returnbreak等導致程序流程轉向的語句
如下例子是不規范的應用其中隱藏了程序的執行流程
#define FOR_ALL for(i = 0; i < SIZE; i++)
/* 數組 c 置0 */
FOR_ALL
{
c = 0;
}
#define CLOSE_FILE { fclose
(fp_local); fclose
(fp_urban);
return; }
<規則7> 使用宏時不允許參數發生變化
下面的例子隱藏了重要的細節隱含了錯誤
#define SQUARE ((x) * (x))
.
.
w = SQUARE(++value);
這個引用將被展開稱
w = ((++value) * (++value));
其中value累加了兩次與設計思想不符正確的用法是
w = SQUARE(x);
x++;
<規則8> 當ifwhilefor等語句的程序塊為摽諗時使用搟}敺擰_
while ( *s++ == *t++ ) ;
以上代碼不符合規范正確的書寫方式為
while( *s++ == *t++ )
{
/* 無循環體 */
}
或
while( *s++ == *t++ )
{
}
<規則9> 結構中元素布局合理一行只定義一個元素
如下例子不符合規范
typedef struct
{
_UI left, top, right, bottom;
} RECT;
應書寫稱
typedef struct
{
_UI left; /* 矩形左側 x 坐標 */
_UI top;
_UI right;
_UI bottom;
} RECT;
<規則10> 枚舉值從小到大順序定義
<規則11> 包含頭文件時使用撓嘍月肪稊不使用摼 月肪稊
-------------------------------------------------------------第 16 頁
如下引用
#include "c:\switch\inc\def.inc"
應改為
#include "inc\def.inc"
或
#include "def.inc"
<規則12> 不允許使用復雜的操作符組合等
下面用法不好
iMaxVal = ( (a > b ? a : b) > c ? (a > b ? a : b) : c );
應該為
iTemp = ( a > b ? a : b);
iMaxVal = (iTemp > b ? iTemp : b);
不要把"++""--"操作符與其他如"+=""-="等組合在一起形成復雜奇怪的表達式如下的
表達式那以理解
*pStatPoi++ += 1;
*++pStatPoi += 1;
應分別改為
*pStatPoi += 1;
pStatPoi++;
和
++pStatPoi;
*pStatPoi += 1;
<規則13> 函數和過程中關系較為緊密的代碼盡可能相鄰
如初始化代碼應放在一起不應在中間插入實現其它功能的代碼以下代碼不符合規范,
for (uiUserNo = 0; uiUserNo < MAX_USER_NO; uiUserNo++)
{
...; /* 初始化用戶數據 */
}
pSamplePointer = NULL;
g_uiCurrentUser = 0; /* 設置當前用戶索引號 */
-------------------------------------------------------------第 17 頁
應必為
for (uiUserNo = 0; uiUserNo < MAX_USER_NO; uiUserNo++)
{
...; /* 初始化用戶數據 */
}
g_uiCurrentUser = 0; /* 設置當前用戶索引號 */
pSamplePointer = NULL;
<規則14> 每個函數的源程序行數原則上應該少于200行
對于消息分流處理函數完成的功能統一但由于消息的種類多可能超過200行的限制
不屬于違反規定
<規則15> 語句嵌套層次不得超過5層
嵌套層次太多增加了代碼的復雜度及測試的難度容易出錯增加代碼維護的難度
<規則16> 用sizeof來確定結構聯合或變量占用的空間
這樣可提高程序的可讀性可維護性同時也增加了程序的可移植性
<規則17> 避免相同的代碼段在多個地方出現
當某段代碼需在不同的地方重復使用時應根據代碼段的規模大小使用函數調用或宏調用的
方式代替這樣對該代碼段的修改就可在一處完成增強代碼的可維護性
<規則18> 使用強制類型轉換
示例
USER_RECORD *pUser;
pUser = (USER_RECORD *) malloc (MAX_USER * sizeof(USER_RECORD));
<規則19> 避免使用 goto 語句
<規則20> 避免產生摮絳蚪釘program knots在循環語句中盡量避免breakgoto的
使用
如下例子
for( i = 0; i < n; i++)
{
bEof = fscanf( pInputFile, "%d;", &x);
if( bEof == EOF )
-------------------------------------------------------------第 18 頁
{
break;
}
nSum += x;
}
最好按以下方式書寫避免程序打摻釘
for( i = 0; i < n && bEof= EOF; i++)
{
bEof = fscanf( pInputFile, "%d;", &x);
if( bEof!= EOF )
{
nSum += x;
}
}
<規則21> 功能相近的一組常量最好使用枚舉來定義
不推薦定義方式
/* 功能寄存器值 */
#define ERR_DATE 1 /* 日期錯誤 */
#define ERR_TIME 2 /* 時間錯誤 */
#define ERR_TASK_NO 3 /* 任務號錯誤 */
推薦按如下方式書寫
/*功能寄存器值 */
enum ERR_TYPE
{
ERR_DATE = 1, /*日期錯誤 */
ERR_TIME = 2, /*時間錯誤 */
ERR_TASK_NO = 3 /* 任務號錯誤 */
}
<規則22> 每個函數完成單一的功能不設計多用途面面俱到的函數
多功能集于一身的函數很可能使函數的理解測試維護等變得困難
使函數功能明確化增加程序可讀性亦可方便維護測試
<建議1> 循環判斷語句的程序塊部分用花括號括起來即使只有一條語句
如
-------------------------------------------------------------第 19 頁
if( bCondition == TRUE )
bFlag = YES;
建議按以下方式書寫
if( bCondition == TRUE )
{
bFlag = YES;
}
這樣做的好處是便于代碼的修改增刪
<建議2> 一行只聲明一個變量
不推薦的書寫方式
void DoSomething(void)
{
int Amicrtmrs, nRC;
int nCode, nStatus;
推薦做法
void DoSomething(void)
{
int nAmicrtmrs; /* ICR 計時器 */
int nRC; /* 返回碼 */
int nCode; /* 訪問碼 */
int nStatus; /* 處理機狀態 */
<建議3> 使用專門的初始化函數對所有的公共變量進行初始化
<建議4> 使用可移植的數據類型盡量不要使用與具體硬件或軟件環境關系密切的變量
<建議5> 用明確的函數實現不明確的語句功能
示例如下語句的功能不很明顯
value = ( a > b ) ? a : b ;
改為如下就很清晰了
int max( int a, int b )
{
return ( ( a > b ) ? a : b ) ;
-------------------------------------------------------------第 20 頁
}
value = max( a, b ) ;
或改為如下
#define MAX( a, b ) ( ( ( a ) > ( b ) ) ? ( a ) : ( b ) )
value = MAX( a, b ) ;
4. 程序正確性效率
<規則1> 嚴禁使用未經初始化的變量
引用未經初始化的變量可能會產生不可預知的后果特別是引用未經初始化的指針經常會導
致系統崩潰需特別注意聲明變量的同時初始化除了能防止引用未經初始化的變量外
還可能生成更高效的機器代碼
<規則2> 定義公共指針的同時對其初始化
這樣便于指針的合法性檢查防止應用未經初始化的指針建議對局部指針也在定義的同時
初始化形成習慣
<規則3> 較大的局部變量2K以上應聲明成靜態類型static 避免占用太多的堆棧空
間
避免發生堆棧溢出出現不可預知的軟件故障
<規則4> 防止內存操作越界
說明內存操作主要是指對數組指針內存地址等的操作內存操作越界是軟件系統主要
錯誤之一后果往往非常嚴重所以當我們進行這些操作時一定要仔細小心
A 數組越界
char aMyArray[10];
for( i = 0; i <= 10; i++ )
{
aMyArray = 0; //當i等于10時將發生越界
}
B 指針操作越界
char aMyArray[10];
char *pMyArray;
-------------------------------------------------------------第 21 頁
pMyArray = aMyArray;
--pMyArray; // 越界
pMyArray = aMyArray;
pMyArray += 10; // 越界
<規則5> 減少沒必要的指針使用特別是較復雜的指針如指針的指針數組的指針指針
的數組函數的指針等
用指針雖然靈活但也對程序的穩定性造成一定威脅主要原因是當要操作一個指針時此
指針可能正指向一個非法的地址安安全全地使用一個指針并不是一件容易的事情
<規則6> 防止引用已經釋放的內存空間
在實際編程過程中稍不留心就會出現在一個模塊中釋放了某個內存塊如指針 而另一
模塊在隨后的某個時刻又使用了它要防止這種情況發生
<規則7> 程序中分配的內存申請的文件句柄在不用時應及時釋放或關閉
分配的內存不釋放以及文件句柄不關閉是較常見的錯誤而且稍不注意就有可能發生這
類錯誤往往會引起很嚴重后果且難以定位
<規則8> 注意變量的有效取值范圍防止表達式出現上溢或下溢
示例
unsigned char cIndex = 10;
while( cIndex-- >= 0 )
{
} //將出現下溢
當cIndex等于0 時再減1不會小于0 而是0xFF故程序是一個死循環
char chr = 127;
chr += 1; //127為chr的邊界值再加1將使chr上溢到-128而不是128
<規則9> 防止精度損失
以下代碼將產生精度丟失
#define DELAY_MILLISECONDS 10000
char time;
time = DELAY_MILLISECONDS;
WaitTime( time );
代碼的本意是想產生10秒鐘的延時然而由于time為字符型變量只取DELAY_MILLISECONDS
的低字節高位字節將丟失結果只產生了16毫秒的延時
-------------------------------------------------------------第 22 頁
<規則10> 防止操易混淆的作符拼寫錯誤
形式相近的操作符最容易引起誤用如C/C++中的=斢霌==敗|斢霌||敗&斢霌&&數齲
羝蔥創砹耍 嘁肫韃灰歡芄患觳槌隼礎_
示例如把&斝闖蓳&&敚蚍 粗_
bRetFlag = ( pMsg -> bRetFlag & RETURN_MASK ) ;
被寫為
bRetFlag = ( pMsg -> bRetFlag && RETURN_MASK ) ;
<規則11> 使用無符號類型定義位域變量
示例
typedef struct
{
int bit1 : 1;
int bit2 : 1;
int bit3 : 1;
} bit;
bit.bit1 = 1;
bit.bit2 = 3;
bit.bit3 = 6;
printf("%d, %d, %d", bit.bit1, bit.bit2, bit.bit3 );
輸出結果為-1,-1, -2不是: 1,3,6.
<規則12> switch語句的程序塊中必須有default語句
對不期望的情況包括異常情況進行處理保證程序邏輯嚴謹
<規則13> 當聲明用于分布式環境或不同CPU間通信環境的數據結構時必須考慮機器的字節
順序使用的位域也要有充分的考慮
比如Intel CPU與68360 CPU在處理位域及整數時其在內存存放的撍承驍正好相反
示例假如有如下短整數及結構
unsigned short int exam ;
typedef struct _EXAM_BIT_STRU
{ /* Intel 68360 */
unsigned int A1 : 1 ; /* bit 0 2 */
unsigned int A2 : 1 ; /* bit 1 1 */
unsigned int A3 : 1 ; /* bit 2 0 */
} _EXAM_BIT ;
-------------------------------------------------------------第 23 頁
如下是Intel CPU生成短整數及位域的方式
內存 0 1 2 ... 從低到高以字節為單位
exam exam低字節 exam高字節
內存 0 bit 1 bit 2 bit ... 字節的各撐粩
_EXAM_BIT A1 A2 A3
如下是68360 CPU生成短整數及位域的方式
內存 0 1 2 ... 從低到高以字節為單位
exam exam高字節 exam低字節
內存 0 bit 1 bit 2 bit ... 字節的各撐粩
_EXAM_BIT A3 A2 A1
<規則14> 編寫可重入函數時應注意局部變量的使用如編寫C/C++語言的可重入函數時
應使用auto即缺省態局部變量或寄存器變量
可重入性是指函數可以被多個任務進程調用在多任務操作系統中函數是否具有可重入性
是非常重要的因為這是多個進程可以共用此函數的必要條件另外編譯器是否提供可重
入函數庫與它所服務的操作系統有關只有操作系統是多任務時編譯器才有可能提供可
重入函數庫如DOS下BC和MSC等就不具備可重入函數庫因為DOS是單用戶單任務操作系
統
編寫C/C++語言的可重入函數時不應使用static局部變量否則必須經過特殊處理才能
使函數具有可重入性
<規則15> 編寫可重入函數時若使用全局變量則應通過關中斷信號量即P V操作
等手段對其加以保護
<規則16> 結構中的位域應盡可能相鄰結構中的位域在開始處應對齊撟紙跀或撟謹的邊
界
這樣可減少結構占用的內存空間減少CPU處理位域的時間提高程序效率
示例如下結構中的位域布局不合理假設例子在Intel CPU環境下
typedef struct _EXAMPLE_STRU
{
unsigned int nExamOne : 6 ;
unsigned int nExamTwo : 3 ; // 此位域跨越字節摻喚訑處
unsigned int nExamThree : 4 ;
} _EXAMPLE ;
應改為如下按字節對齊
typedef struct _EXAMPLE_STRU
{
unsigned int nExamOne : 6 ;
unsigned int nFreeOne : 2 ; // 保留bit位使下個位域從字節開始
unsigned int nExamTwo : 3 ; // 此位域從新的字節處開始
unsigned int nExamThree : 4 ;
} _EXAMPLE ;
<規則17> 避免函數中不必要語句防止程序中的垃圾代碼預留代碼應以注釋的方式出
現
程序中的垃圾代碼不僅占用額外的空間而且還常常影響程序的功能與性能很可能給程序
的測試維護等造成不必要的麻煩
<規則18> 通過對系統數據結構的劃分與組織的改進以及對程序算法的優化來提高空間效
率
這種方式是解決軟件空間效率的根本辦法
示例如下記錄學生學習成績的結構不合理
typedef unsigned char _UC ;
typedef unsigned int _UI ;
typedef struct _STUDENT_SCORE_STRU
{
_UC szName[ 8 ] ;
_UC cAge ;
_UC cSex ;
_UC cClass ;
_UC cSubject ;
float fScore ;
} _STUDENT_SCORE ;
因為每位學生都有多科學習成績故如上結構將占用較大空間應如下改進分為兩個結
構 總的存貯空間將變小操作也變得更方便
typedef struct _STUDENT_STRU
{
_UC szName[ 8 ] ;
_UC cAge ;
_UC cSex ;
_UC cClass ;
} _STUDENT ;
typedef struct _STUDENT_SCORE_STRU
{
_UI iStudentIndex ;
_UC cSubject ;
float fScore ;
} _STUDENT_SCORE ;
<規則19> 循環體內工作量最小化
應仔細考慮循環體內的語句是否可以放在循環體之外使循環體內工作量最小從而提高程
序的時間效率
示例如下代碼效率不高
for ( i= 0 ; i< MAX_ADD_NUMBER ; i++ )
{
nSum += i;
nBackSum = nSum ; /* 備份和 */
}
語句搉BackSum = nSum ;斖耆 梢苑旁趂or語句之后如下
for ( i = 0 ; i < MAX_ADD_NUMBER ; i ++ )
{
nSum += i ;
}
nBackSum = nSum ; /*備份和 */
<規則20> 在多重循環中應將最忙的循環放在最內層
<規則21> 避免循環體內含判斷語句將與循環變量無關的判斷語句移到循環體外
目的是減少判斷次數循環體中的判斷語句是否可以移到循環體外要視程序的具體情況而
言一般情況與循環變量無關的判斷語句可以移到循環體外而有關的則不可以
<規則22> 盡量用乘法或其它方法代替除法特別是浮點運算中的除法在時間效率要求不
是特別嚴格時要優先保證程序的可讀性
說明浮點運算除法要占用較多CPU資源
示例如下表達式運算可能要占較多CPU資源
#define PAI 3.1416
fRadius = fCircleLength / ( 2 * PAI ) ;
應如下把浮點除法改為浮點乘法
#define PAI_RECIPROCAL ( 1 / 3.1416 ) // 編譯器編譯時將生成具體浮點數
fRadius = fCircleLength * PAI_RECIPROCAL / 2 ;
<規則23> 用++敚--敳僮鞔 鎿+=1敚-=1敚 岣叱絳蛩俁取_
<規則24> 系統輸入如用戶輸入 系統輸出如信息包輸出系統資源操作如內存
分配文件及目錄操作網絡操作如通信調用等 任務之間的操作如通信調用
等時必須進行錯誤超時或者異常處理
<建議 1> 定義字符串變量的同時將其初始化為空即摂以避免無限長字符串
<建議 2> 在switch語句中將經常性的處理放在前面
2.5. 接口
<規則1> 頭文件應采用 #ifndef / #define / #endif 的方式來防止多次被嵌入
示例如下
假設頭文件為揇EF.INC"則其內容應為
#ifndef __DEF_INC
#define __DEF_INC
...
#endif
<規則2> 去掉沒有必要的公共變量編程時應盡量少用公共變量
公共變量是增大模塊間耦合的原因之一故應減少沒必要的公共變量以降低模塊間的耦合
度應該構造僅有一個模塊或函數可以修改創建而其余有關模塊或函數只訪問的公共變
量防止多個不同模塊或函數都可以修改創建同一公共變量的現象
<規則3> 當向公共變量傳遞數據時要防止越界現象發生
對公共變量賦值時若有必要應進行合法性檢查以提高代碼的可靠性穩定性
<規則4> 返回值為指針的函數不可將局部變量的地址作為返回值
當函數退出時非static局部變量將消失所以引用返回的指針將可能引起嚴重后果下例
將不能完成正確的功能
char *GetFilename(int nFileNo)
{
char szFileName[20];
sprintf( szFileName, "COUNT%d", nFileNo);
return szFileName;
}
<規則5> 盡量不設計多參數函數將不使用的參數從接口中去掉降低接口復雜度
減少函數間接口的復雜度
<規則6> 對所調用函數的返回碼要仔細全面地處理
防止把錯誤傳遞到后面的處理流程如有意不檢查其返回碼應明確指明
如
(void)fclose(fp);
<規則7> 顯示地給出函數的返回值類型無返回值函數定義為void
C C++語言的編譯系統默認無顯示返回值函數的返回值類型為int
<規則8> 聲明函數原型時給出參數名稱和類型并且與實現此函數時的參數名稱類型保持
一致無參數的函數用void聲明
示例下面聲明不正確
int CheckData( ) ;
int SetPoint( int, int ) ;
int SetPoint( x, y )
int x, y;
應改為如下聲明
int CheckData( void ) ;
int SetPoint( int x, int y ) ;
<規則9> 檢查接口函數所有輸入參數的有效性
可直接檢查或使用斷言進行檢查尤其是指針參數只在本模塊內使用的函數可不
檢查
<規則10> 檢查函數的所有非參數輸入如數據文件公共變量等
可直接檢查或使用斷言進行檢查尤其是指針變量
<規則11> 聲明函數原型時對于數組型參數不要聲明為指針維護函數接口的清晰性
示例假設函數SortInt()完成的功能是對一組整數排序接受的參數是一整數數
-------------------------------------------------------------第 28 頁
組及數組中的元素個數以下聲明不符合規范
void SortInt(int num, int *data);
應聲明為
void SortInt(int num, int data[]);
2.6.代碼可測性
<規則1> 模塊編寫應該有完善的測試方面的考慮
<規則2> 源代碼中應該設計了代碼測試的內容如打印宏開關變量值函數名稱函數值
等
在編寫代碼之前應預先設計好程序調試與測試的方法和手段并設計好各種調測開關及相
應測試代碼如打印函數等
程序的調試與測試是軟件生存周期中很重要的一個階段如何對軟件進行較全面高率的測
試并盡可能地找出軟件中的錯誤就成為很關鍵的問題因此在編寫源代碼之前除了要有一
套比較完善的測試計劃外還應設計出一系列代碼測試手段為單元測試集成測試及系統
聯調提供方便
<規則3> 在同一項目組或產品組內要有一套統一的為集成測試與系統聯調準備的調測開關
及相應打印函數并且要有詳細的說明
本規則是針對項目組或產品組的
示例.ext文件示例文件名為EXAMPLE.EXT
/* 頭文件開始 */
#ifndef __EXAMPLE_EXT
#define __EXAMPLE_EXT
#define _EXAMPLE_DEBUG_ // 模塊測試總開關打開開關的含義是模塊可以
// 進行單元測試或其它功能目的等的測試
#ifdef _EXAMPLE_DEBUG_
#define _EXAMPLE_UNIT_TEST_ // 單元測試宏開關
#define _EXAMPLE_ASSERT_TEST_ // 斷言測試開關
... // 其它測試開關
#endif
#ifndef _EXAMPLE_UNIT_TEST_ // 若沒有定義單元測試
-------------------------------------------------------------第 29 頁
#include
#include
#ifndef _SYSTEM_DEBUG_VERSION_ // 如果是發行版本即非DEBUG版
#undef _EXAMPLE_UNIT_TEST_
#undef _EXAMPLE_ASSERT_TEST_
... // 將所有與測試有關的開關都關掉即編譯時不含任何測試代碼
#endif
#include
... // 其它接口頭文件
#else // 若定義了單元測試則應構造單元測試所需的環境結構等
typdef unsigned char _UC ;
typdef unsigned long _UL ;
#define TRUE 1
... // 所有為單元測試準備的環境如宏枚舉結構聯合等
#endif
#endif /* EXAMPLE.EXT結束 */
/* 頭文件結束 */
<規則4> 在同一項目組或產品組內調測打印出的信息串的格式要有統一的形式信息串中
至少要有所在模塊名或源文件名及行號
統一的調測信息格式便于集成測試
<規則5> 使用斷言來發現軟件問題提高代碼可測性
斷言是對某種假設條件進行檢查可理解為若條件成立則無動作否則應報告 它可以快
速發現并定位軟件問題同時對系統錯誤進行自動報警斷言可以對在系統中隱藏很深用
其它手段極難發現的問題進行定位從而縮短軟件問題定位時間提高系統的可測性實際
應用時可根據具體情況靈活地設計斷言
示例下面是C語言中的一個斷言用宏來設計的其中NULL為0L
#ifdef _EXAM_ASSERT_TEST_ // 若使用斷言測試
void ExamAssert( char * szFileName, unsigned int nLineNo )
{
printf( "\n[EXAM] Assert failed: %s, line %u\n",
szFileName, nLineNo ) ;
abort( ) ;
}
#define EXAM_ASSERT( condition ) if ( condition )
\ //
若條件成立則無動作
NULL ; else \ //
否則報告
ExamAssert( __FILE__, __LINE__ )
#else // 若不使用斷言測試
#define EXAM_ASSERT( condition ) NULL
#endif /* ASSERT結束 */
<規則6> 用斷言來檢查程序正常運行時不應發生但在調測時有可能發生的非法情況
<規則7> 不能用斷言代替錯誤處理來檢查最終產品肯定會出現且必須處理的錯誤情況
如某模塊收到其它模塊或鏈路上的消息后要對消息的合理性進行檢查此過程為正常的錯
誤檢查不能用斷言來代替
評論
查看更多