C語言:程式設計規範
-
-
- 程式碼總體原則
- 術語定義
- 1標頭檔案
-
- 原則1.1 標頭檔案中適合放置介面的宣告,不適合放置實現。
- 原則1.2 標頭檔案應當職責單一。
- 原則1.3 標頭檔案應向穩定的方向包含。
- 規則1.1 每一個.c檔案應有一個同名.h檔案,用於宣告需要對外公開的介面。
- 規則1.2 禁止標頭檔案迴圈依賴。
- 規則1.3 .c/.h檔案禁止包含用不到的標頭檔案。
- 規則1.4 標頭檔案應當自包含。
- 規則1.5 總是編寫內部#include保護符(#define 保護)。
- 規則1.6 禁止在標頭檔案中定義變數。
- 規則1.7 只能通過包含標頭檔案的方式使用其他.c提供的介面,禁止在.c中通過extern的方式使用外部函式介面、變數。
- 規則1.8 禁止在extern “C”中包含標頭檔案。
- 建議1.1 一個模組通常包含多個.c檔案,建議放在同一個目錄下,目錄名即為模組名。為方便外部使用者,建議每一個模組提供一個.h,檔名為目錄名。
- 建議1.2 如果一個模組包含多個子模組,則建議每一個子模組提供一個對外的.h,檔名為子模組名。
- 建議1.3 標頭檔案不要使用非習慣用法的副檔名,如.inc。
- 建議1.4 同一產品統一包含標頭檔案排列方式。
-
- 2函式
-
- 原則2.1 一個函式僅完成一件功能。
- 原則2.2 重複程式碼應該儘可能提煉成函式。
- 規則2.1 避免函式過長,新增函式不超過50行(非空非註釋行)。
- 規則2.2 避免函式的程式碼塊巢狀過深,新增函式的程式碼塊巢狀不超過4層。
- 規則2.3 可重入函式應避免使用共享變數;若需要使用,則應通過互斥手段(關中斷、訊號量)對其加以保護。
- 規則2.4 對引數的合法性檢查,由呼叫者負責還是由介面函式負責,應在專案組/模組內應統一規定。預設由呼叫者負責。
- 規則2.5 對函式的錯誤返回碼要全面處理。
- 規則2.6 設計高扇入,合理扇出(小於7)的函式。
- 規則2.7 廢棄程式碼(沒有被呼叫的函式和變數)要及時清除。
- 建議2.1 函式不變引數使用const。
- 建議2.2 函式應避免使用全域性變數、靜態區域性變數和I/O操作,不可避免的地方應集中使用。
- 建議2.3 檢查函式所有非引數輸入的有效性,如資料檔案、公共變數等。
- 建議2.4 函式的引數個數不超過5個。
- 建議2.5 除列印類函式外,不要使用可變長參函式。
- 建議2.6 在原始檔範圍內宣告和定義的所有函式,除非外部可見,否則應該增加static關鍵字。
-
- 3識別符號命名與定義
- 4變數
-
- 原則4.1 一個變數只有一個功能,不能把一個變數用作多種用途。
- 原則4.2 結構功能單一;不要設計面面俱到的資料結構。
- 原則4.3 不用或者少用全域性變數。
- 規則4.1 防止區域性變數與全域性變數同名。
- 規則4.2 通訊過程中使用的結構,必須注意位元組序。
- 規則4.3 嚴禁使用未經初始化的變數作為右值。
- 建議4.1 構造僅有一個模組或函式可以修改、建立,而其餘有關模組或函式只訪問的全域性變數,防止多個不同模組或函式都可以修改、建立同一全域性變數的現象。
- 建議4.2 使用面向介面程式設計思想,通過API訪問資料:如果本模組的資料需要對外部模組開放,應提供介面函式來設定、獲取,同時注意全域性資料的訪問互斥。
- 建議4.3 在首次使用前初始化變數,初始化的地方離使用的地方越近越好。
- 建議4.4 明確全域性變數的初始化順序,避免跨模組的初始化依賴。
- 建議4.5 儘量減少沒有必要的資料型別預設轉換與強制轉換。
-
- 5巨集、常量
- 6質量保證
-
- 原則6.1 程式碼質量保證優先原則
- 原則6.2 要時刻注意易混淆的操作符。
- 原則6.3 必須瞭解編譯系統的記憶體分配方式,特別是編譯系統對不同型別的變數的記憶體分配規則,如區域性變數在何處分配、靜態變數在何處分配等。
- 原則6.4 不僅關注介面,同樣要關注實現。
- 規則6.1 禁止記憶體操作越界。
- 規則6.2 禁止記憶體洩漏。
- 規則6.3 禁止引用已經釋放的記憶體空間。
- 規則6.4 程式設計時,要防止差1錯誤。
- 規則6.5 所有的if … else if結構應該由else子句結束 ;switch語句必須有default分支。
- 建議6.1 函式中分配的記憶體,在函式退出之前要釋放。
- 建議6.2 if語句儘量加上else分支,對沒有else分支的語句要小心對待。
- 建議6.3 不要濫用goto語句。
- 建議6.4 時刻注意表示式是否會上溢、下溢。
-
- 7程式效率
- 8註釋
-
- 原則8.1 優秀的程式碼可以自我解釋,不通過註釋即可輕易讀懂。
- 原則8.2 註釋的內容要清楚、明瞭,含義準確,防止註釋二義性。
- 原則8.3 在程式碼的功能、意圖層次上進行註釋,即註釋解釋程式碼難以直接表達的意圖,而不是重複描述程式碼。
- 規則8.1 修改程式碼時,維護程式碼周邊的所有註釋,以保證註釋與程式碼的一致性。不再有用的註釋要刪除。
- 規則8.2 檔案頭部應進行註釋,註釋必須列出:版權說明、版本號、生成日期、作者姓名、工號、內容、功能說明、與其它檔案的關係、修改日誌等,標頭檔案的註釋中還應有函式功能簡要說明。
- 規則8.3 函式宣告處註釋描述函式功能、效能及用法,包括輸入和輸出引數、函式返回值、可重入的要求等;定義處詳細描述函式功能和實現要點,如實現的簡要步驟、實現的理由、設計約束等。
- 規則8.4 全域性變數要有較詳細的註釋,包括對其功能、取值範圍以及存取時注意事項等的說明。
- 規則8.5 註釋應放在其程式碼上方相鄰位置或右方,不可放在下面。如放於上方則需與其上面的程式碼用空行隔開,且與下方程式碼縮排相同。
- 規則8.6 對於switch語句下的case語句,如果因為特殊情況需要處理完一個case後進入下一個case處理,必須在該case語句處理完、下一個case語句前加上明確的註釋。
- 規則8.7 避免在註釋中使用縮寫,除非是業界通用或子系統內標準化的縮寫。
- 規則8.8 同一產品或專案組統一註釋風格。
- 建議8.1 避免在一行程式碼或表示式的中間插入註釋。
- 建議8.2 註釋應考慮程式易讀及外觀排版的因素,使用的語言若是中、英兼有的,建議多使用中文,除非能用非常流利準確的英文表達。對於有外籍員工的,由產品確定註釋語言。
- 建議8.3 檔案頭、函式頭、全域性常量變數、型別定義的註釋格式採用工具可識別的格式。
-
- 9排版與格式
-
- 規則9.1 程式塊採用縮排風格編寫,每級縮排為4個空格。
- 規則9.2 相對獨立的程式塊之間、變數說明之後必須加空行。
- 規則9.3 一條語句不能過長,如不能拆分需要分行寫。一行到底多少字元換行比較合適,產品可以自行確定。
- 規則9.4 多個短語句(包括賦值語句)不允許寫在同一行內,即一行只寫一條語句。
- 規則9.5 if、for、do、while、case、switch、default等語句獨佔一行。
- 規則9.6 在兩個以上的關鍵字、變數、常量進行對等操作時,它們之間的操作符之前、之後或者前後要加空格;進行非對等操作時,如果是關係密切的立即操作符(如->),後不應加空格。
- 建議9.1 註釋符(包括„/‟„//‟„/‟)與註釋內容之間要用一個空格進行分隔。
- 建議9.2 源程式中關係較為緊密的程式碼應儘可能相鄰。
-
- 10表示式
- 11程式碼編輯、編譯
- 12可測性
- 13安全性
- 14程式碼測試
- 15可移植性
- 16業界程式設計規範
-
程式碼總體原則
1、清晰第一
清晰性是易於維護、易於重構的程式必需具備的特徵。程式碼首先是給人讀的,一般情況下,程式碼的可閱讀性高於效能,只有確定性能是瓶頸時,才應該主動優化。
2、簡潔為美
簡潔就是易於理解並且易於實現。程式碼越長越難以看懂,也就越容易在修改時引入錯誤。寫的程式碼越多,意味著出錯的地方越多,也就意味著程式碼的可靠性越低。
廢棄的程式碼(沒有被呼叫的函式和全域性變數)要及時清除,重複程式碼應該儘可能提煉成函式。
3、選擇合適的風格,與程式碼原有風格保持一致
如果重構/修改其他風格的程式碼時,比較明智的做法是根據現有程式碼的現有風格繼續編寫程式碼。
術語定義
原則:程式設計時必須堅持的指導思想。
規則:程式設計時強制必須遵守的約定。
建議:程式設計時必須加以考慮的約定。
說明:對此原則/規則/建議進行必要的解釋。
示例:對此原則/規則/建議從正、反兩個方面給出例子。
延伸閱讀材料:建議進一步閱讀的參考材料。
1標頭檔案
原則1.1 標頭檔案中適合放置介面的宣告,不適合放置實現。
說明:標頭檔案是模組(Module)或單元(Unit)的對外介面。標頭檔案中應放置對外部的宣告,如對外提供的函式宣告、巨集定義、型別定義等。
內部使用的函式(相當於類的私有方法)宣告不應放在標頭檔案中。
內部使用的巨集、列舉、結構定義不應放入標頭檔案中。
變數定義不應放在標頭檔案中,應放在.c檔案中。
變數的宣告儘量不要放在標頭檔案中,亦即儘量不要使用全域性變數作為介面。變數是模組或單元的內部實現細節,不應通過在標頭檔案中宣告的方式直接暴露給外部,應通過函式介面的方式進行對外暴露。 即使必須使用全域性變數,也只應當在.c中定義全域性變數,在.h中僅宣告變數為全域性的。
原則1.2 標頭檔案應當職責單一。
說明:標頭檔案過於複雜,依賴過於複雜是導致編譯時間過長的主要原因。很多現有程式碼中標頭檔案過大,職責過多,再加上迴圈依賴的問題,可能導致為了在.c中使用一個巨集,而包含十幾個標頭檔案。
原則1.3 標頭檔案應向穩定的方向包含。
說明:標頭檔案的包含關係是一種依賴,一般來說,應當讓不穩定的模組依賴穩定的模組,從而當不穩定的模組發生變化時,不會影響(編譯)穩定的模組。
規則1.1 每一個.c檔案應有一個同名.h檔案,用於宣告需要對外公開的介面。
說明:如果一個.c檔案不需要對外公佈任何介面,則其就不應當存在,除非它是程式的入口,如main函式所在的檔案。
示例:對於如下場景,如在一個.c中存在函式呼叫關係:
void foo()
{
bar();
}
void bar()
{
Do something;
}
必須在foo之前宣告bar,否則會導致編譯錯誤。
這一類的函式宣告,應當在.c的頭部宣告,並宣告為static的,如下:
static void bar();
規則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)不要在受保護部分的前後放置程式碼或者註釋。
例外情況:標頭檔案的版權宣告部分以及標頭檔案的整體註釋部分(如闡述此標頭檔案的開發背景、使用注意事項等)可以放在保護符(#ifndef XX_H)前面。
示例:假定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
規則1.6 禁止在標頭檔案中定義變數。
說明:在標頭檔案中定義變數,將會由於標頭檔案被其他.c檔案包含而導致變數重複定義。
規則1.7 只能通過包含標頭檔案的方式使用其他.c提供的介面,禁止在.c中通過extern的方式使用外部函式介面、變數。
說明:若A.c使用了B.c定義的foo()函式,則應當在B.h中宣告extern int foo(int input);並在A.c中通過#include
規則1.8 禁止在extern “C”中包含標頭檔案。
建議1.1 一個模組通常包含多個.c檔案,建議放在同一個目錄下,目錄名即為模組名。為方便外部使用者,建議每一個模組提供一個.h,檔名為目錄名。
建議1.2 如果一個模組包含多個子模組,則建議每一個子模組提供一個對外的.h,檔名為子模組名。
建議1.3 標頭檔案不要使用非習慣用法的副檔名,如.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);
.....
buffer =(char *)realloc(buffer, NEW_SIZE);
如果沒有足夠可用的記憶體用來完成重新分配,函式返回為NULL,導致buffer原來指向的記憶體被丟失。
原則2.2 重複程式碼應該儘可能提煉成函式。
說明:重複程式碼提煉成函式可以帶來維護成本的降低。
重複程式碼是我司不良程式碼最典型的特徵之一。在“程式碼能用就不改”的指導原則之下,大量的煙囪式設計及其實現充斥著各產品程式碼之中。新需求增加帶來的程式碼拷貝和修改,隨著時間的遷移,產品中堆砌著許多類似或者重複的程式碼。
專案組應當使用 ,在持續整合環境中持續檢查程式碼重複度指標變化趨勢,並對新增重複程式碼及時重構。當一段程式碼重複超過三次時,應當立刻著手消除重複。
規則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)
{
TmoCnt = 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;
}
}
}
規則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 對引數的合法性檢查,由呼叫者負責還是由介面函式負責,應在專案組/模組內應統一規定。預設由呼叫者負責。
規則2.5 對函式的錯誤返回碼要全面處理。
說明:一個函式(標準庫中的函式/第三方庫函式/使用者定義的函式)能夠提供一些指示錯誤發生的方法。這可以通過使用錯誤標記、特殊的返回資料或者其他手段,不管什麼時候函式提供了這樣的機制,呼叫程式應該在函式返回時立刻檢查錯誤指示。
示例:下面的程式碼導致宕機
FILE *fp = fopen( "./writeAlarmLastTime.log","r");
char buff[128] = "";
fscanf(fp,“%s”,buff); /* 讀取最新的告警時間;由於檔案writeAlarmLastTime.log為空,導致buff為空 */
fclose(fp);
long fileTime = getAlarmTime(buff); /* 解析獲取最新的告警時間;getAlarmTime函式未檢查buff指標,導致宕機 */
正確寫法:
FILE *fp = fopen( "./writeAlarmLastTime.log","r");
char buff[128] = "";
if ( EOF == fscanf(fp,“%s”,buff) ) //檢查函式fscanf的返回值,確保讀到資料
{
return ;
}
fclose(fp);
long fileTime = getAlarmTime(buff); //解析獲取最新的告警時間;
規則2.6 設計高扇入,合理扇出(小於7)的函式。
說明:扇出是指一個函式直接呼叫(控制)其它函式的數目,而扇入是指有多少上級函式呼叫它。
扇出過大,表明函式過分複雜,需要控制和協調過多的下級函式;而扇出過小,例如:總是1,表明函式的呼叫層次可能過多,這樣不利於程式閱讀和函式結構的分析,並且程式執行時會對系統資源如堆疊空間等造成壓力。通常函式比較合理的扇出(排程函式除外)通常是3~5。
扇出太大,一般是由於缺乏中間層次,可適當增加中間層次的函式。扇出太小,可把下級函式進一步分解多個函式,或合併到上級函式中。當然分解或合併函式時,不能改變要實現的功能,也不能違背函式間的獨立性。
扇入越大,表明使用此函式的上級函式越多,這樣的函式使用效率高,但不能違背函式間的獨立性而單純地追求高扇入。公共模組中的函式及底層函式應該有較高的扇入。
較良好的軟體結構通常是頂層函式的扇出較高,中層函式的扇出較少,而底層函式則扇入到公共模組中。
規則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;
}
建議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;
}
建議2.3 檢查函式所有非引數輸入的有效性,如資料檔案、公共變數等。
說明:函式的輸入主要有兩種:一種是引數輸入;另一種是全域性變數、資料檔案的輸入,即非引數輸入。函式在使用輸入引數之前,應進行有效性檢查。
示例:下面的程式碼導致宕機
hr = pRootNode->get_firstChild(&pLogItem); // list.xml 為空,導致讀出pLogItem為空
…..
hr = pLogItem->get_nextSibling(&pMediaNextNode); // pLogItem為空,導致宕機
正確寫法:確保讀出的內容非空。
hr