作為程序開發者,避免不了閱讀別人代碼,那么就會涉及到到一門語言的編程規范。規范雖然不是語言本身的硬性要求,但是已經是每一個語言使用者約定俗成的一個規范。
按照編程規范編寫的代碼,至少在代碼閱讀時,給人一種愉悅的心情,特別是強迫癥患者。另一方面,統一的編程風格,可以減少編寫錯誤,利于后期維護。
因為最近又開始進行純C語言的開發,并且是基于SDK的開發,所以添加的每一行代碼都應該與原來風格保持一致,不能因為一顆老鼠屎壞了一鍋湯。一個良好的編程規范也可以看出編程人員的細心程度與代碼質量。
之前待過的兩家公司,也都有各自總結的編程規范,但都不約而同的一致,適用本公司的軟件開發。這幾天有幸可以參閱華為技術有限公司的C語言編程規范,相比之下,寫的更加詳細。
至少接觸到了,在這個編程規范中體現了,并且還擴充了很多,我覺得有必要歸納總結,一遍日后查閱。先是學習規范,然后再積累規范,最后才是依規范編寫。
1、清晰第一
清晰性是易于維護、易于重構的程序必需具備的特征。代碼首先是給人讀的,好的代碼應當可以像文章一樣發聲朗誦出來。
2.、簡潔為美
簡潔就是易于理解并且易于實現。代碼越長越難以看懂,也就越容易在修改時引入錯誤。寫的代碼越多,意味著出錯的地方越多,也就意味著代碼的可靠性越低。因此,我們提倡大家通過編寫簡潔明了的代碼來提升代碼可靠性。廢棄的代碼(沒有被調用的函數和全局變量)要及時清除,重復代碼應該盡可能提煉成函數。
3、選擇合適的風格,與代碼原有的風格保持一致
產品所有人共同分享同一種風格所帶來的好處,遠遠超出為了統一而付出的代價。在公司已有編碼規范的指導下,審慎地編排代碼以使代碼盡可能清晰,是一項非常重要的技能。如果重構/修改其他風格的代碼時,比較明智的做法是根據現有代碼的現有風格繼續編寫代碼,或者使用格式轉換工具進行轉換成公司內部風格。
一、頭文件
原則1.1 頭文件中適合放置接口的聲明,不適合放置實現。
說明:頭文件是模塊(Module)或單元(Unit)的對外接口。頭文件中應放置對外部的聲明,如對外提供的函數聲明、宏定義、類型定義等。
原則1.2 頭文件應當職責單一。
說明:頭文件過于復雜,依賴過于復雜是導致編譯時間過長的主要原因。很多現有代碼中頭文件過大,職責過多,再加上循環依賴的問題,可能導致為了在.c中使用一個宏,而包含十幾個頭文件。
原則1.3 頭文件應向穩定的方向包含。
說明:頭文件的包含關系是一種依賴,一般來說,應當讓不穩定的模塊依賴穩定的模塊,從而當不穩定的模塊發生變化時,不會影響(編譯)穩定的模塊。
規則1.1 每一個.c文件應有一個同名.h文件,用于聲明需要對外公開的接口。
說明:如果一個.c文件不需要對外公布任何接口,則其就不應當存在,除非它是程序的入口,如main函數所在的文件。
規則1.2 禁止頭文件循環依賴。
說明:頭文件循環依賴,指a.h包含b.h,b.h包含c.h,c.h包含a.h之類導致任何一個頭文件修改,都導致所有包含了a.h/b.h/c.h的代碼全部重新編譯一遍。
而如果是單向依賴,如a.h包含b.h,b.h包含c.h,而c.h不包含任何頭文件,則修改a.h不會導致包含了b.h/c.h的源代碼重新編譯。
規則1.3 .c/.h文件禁止包含用不到的頭文件。
說明:很多系統中頭文件包含關系復雜,開發人員為了省事起見,可能不會去一一鉆研,直接包含一切想到的頭文件,甚至有些產品干脆發布了一個god.h,其中包含了所有頭文件,然后發布給各個項目組使用,這種只圖一時省事的做法,導致整個系統的編譯時間進一步惡化,并對后來人的維護造成了巨大的麻煩。
規則1.4 頭文件應當自包含。
說明:簡單的說,自包含就是任意一個頭文件均可獨立編譯。如果一個文件包含某個頭文件,還要包含另外一個頭文件才能工作的話,就會增加交流障礙,給這個頭文件的用戶增添不必要的負擔。
規則1.5 總是編寫內部#include保護符(#define 保護)。
說明:多次包含一個頭文件可以通過認真的設計來避免。如果不能做到這一點,就需要采取阻止頭文件內容被包含多于一次的機制。
注: 沒有在宏最前面加上 _ ,即使用 FILENAME_H代替 ?FILENAME_H ,是因為一般以 _ 和 ?__ 開頭的標識符為系統保留或者標準庫使用,在有些靜態檢查工具中,若全局可見的標識符以 _ 開頭會給出告警。
定義包含保護符時,應該遵守如下規則:
1)保護符使用唯一名稱;
2)不要在受保護部分的前后放置代碼或者注釋。
規則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"中包含頭文件。
說明:在extern "C"中包含頭文件,會導致extern "C"嵌套,Visual Studio對extern "C"嵌套層次有限制,嵌套層次太多會編譯錯誤。
建議1.1 一個模塊通常包含多個.c文件,建議放在同一個目錄下,目錄名即為模塊名。為方便外部使用者,建議每一個模塊提供一個.h,文件名為目錄名。
建議1.2 如果一個模塊包含多個子模塊,則建議每一個子模塊提供一個對外的.h,文件名為子模塊名。
建議1.3 頭文件不要使用非習慣用法的擴展名,如.inc。
建議1.4 同一產品統一包含頭文件排列方式。
二、函數
原則2.1 一個函數僅完成一件功能。
說明:一個函數實現多個功能給開發、使用、維護都帶來很大的困難。
原則2.2 重復代碼應該盡可能提煉成函數。
說明:重復代碼提煉成函數可以帶來維護成本的降低。
規則2.1 避免函數過長,新增函數不超過50行(非空非注釋行)。
說明:本規則僅對新增函數做要求,對已有函數修改時,建議不增加代碼行。
規則2.2 避免函數的代碼塊嵌套過深,新增函數的代碼塊嵌套不超過4層。
說明:本規則僅對新增函數做要求,對已有的代碼建議不增加嵌套層次。
規則2.3 可重入函數應避免使用共享變量;若需要使用,則應通過互斥手段(關中斷、信號量)對其加以保護。
規則2.4 對參數的合法性檢查,由調用者負責還是由接口函數負責,應在項目組/模塊內應統一規定。缺省由調用者負責。
規則2.5 對函數的錯誤返回碼要全面處理。
規則2.6 設計高扇入,合理扇出(小于7)的函數。
說明:扇出是指一個函數直接調用(控制)其它函數的數目,而扇入是指有多少上級函數調用它。如下圖:
img
規則2.7 廢棄代碼(沒有被調用的函數和變量)要及時清除。
建議2.1 函數不變參數使用const。
說明:不變的值更易于理解/跟蹤和分析,把const作為默認選項,在編譯時會對其進行檢查,使代碼更牢固/更安全。
建議2.2 函數應避免使用全局變量、靜態局部變量和I/O操作,不可避免的地方應集中使用。
建議2.3 檢查函數所有非參數輸入的有效性,如數據文件、公共變量等。
說明:函數的輸入主要有兩種:一種是參數輸入;另一種是全局變量、數據文件的輸入,即非參數輸入。函數在使用輸入參數之前,應進行有效性檢查。
建議2.4 函數的參數個數不超過5個。
建議2.5 除打印類函數外,不要使用可變長參函數。
建議2.6 在源文件范圍內聲明和定義的所有函數,除非外部可見,否則應該增加static關鍵字。
三、 標識符命名與定義
目前比較常用的如下幾種命名風格:
unix like風格:單詞用小寫字母,每個單詞直接用下劃線_分割,,例如text_mutex,kernel_text_address。
Windows風格:大小寫字母混用,單詞連在一起,每個單詞首字母大寫。不過Windows風格如果遇到大寫專有用語時會有些別扭,例如命名一個讀取RFC文本的函數,命令為ReadRFCText,看起來就沒有unix like的read_rfc_text清晰了。
原則3.1 標識符的命名要清晰、明了,有明確含義,同時使用完整的單詞或大家基本可以理解的縮寫,避免使人產生誤解。
原則3.2 除了常見的通用縮寫以外,不使用單詞縮寫,不得使用漢語拼音。
建議3.1 產品/項目組內部應保持統一的命名風格。
建議3.2 盡量避免名字中出現數字編號,除非邏輯上的確需要編號。
建議3.3 標識符前不應添加模塊、項目、產品、部門的名稱作為前綴。
建議3.4 平臺/驅動等適配代碼的標識符命名風格保持和平臺/驅動一致。
建議3.5 重構/修改部分代碼時,應保持和原有代碼的命名風格一致。
建議3.6 文件命名統一采用小寫字符。
規則3.2 全局變量應增加“g_”前綴。
規則3.3 靜態變量應增加“s_”前綴。
規則3.4 禁止使用單字節命名變量,但允許定義i、j、k作為局部循環變量。
建議3.7 不建議使用匈牙利命名法。
說明:變量命名需要說明的是變量的含義,而不是變量的類型。在變量命名前增加類型說明,反而降低了變量的可讀性;更麻煩的問題是,如果修改了變量的類型定義,那么所有使用該變量的地方都需要修改。
建議3.8 使用名詞或者形容詞+名詞方式命名變量。
建議3.9 函數命名應以函數要執行的動作命名,一般采用動詞或者動詞+名詞的結構。
建議3.10 函數指針除了前綴,其他按照函數的命名規則命名。
規則3.5 對于數值或者字符串等等常量的定義,建議采用全大寫字母,單詞之間加下劃線?_?的方式命名(枚舉同樣建議使用此方式定義)。
規則3.6 除了頭文件或編譯開關等特殊標識定義,宏定義不能使用下劃線?_?開頭和結尾。
四、變量
原則4.1 一個變量只有一個功能,不能把一個變量用作多種用途。
原則4.2 結構功能單一;不要設計面面俱到的數據結構。
原則4.3 不用或者少用全局變量。
規則4.1 防止局部變量與全局變量同名。
規則4.2 通訊過程中使用的結構,必須注意字節序。
規則4.3 嚴禁使用未經初始化的變量作為右值。
建議4.1 構造僅有一個模塊或函數可以修改、創建,而其余有關模塊或函數只訪問的全局變量,防止多個不同模塊或函數都可以修改、創建同一全局變量的現象。
建議4.2 使用面向接口編程思想,通過API訪問數據:如果本模塊的數據需要對外部模塊開放,應提供接口函數來設置、獲取,同時注意全局數據的訪問互斥。
建議4.3 在首次使用前初始化變量,初始化的地方離使用的地方越近越好。
建議4.4 明確全局變量的初始化順序,避免跨模塊的初始化依賴。
說明:系統啟動階段,使用全局變量前,要考慮到該全局變量在什么時候初始化,使用全局變量和初始化全局變量,兩者之間的時序關系,誰先誰后,一定要分析清楚,不然后果往往是低級而又災難性的。
建議4.5 盡量減少沒有必要的數據類型默認轉換與強制轉換。
說明:當進行數據類型強制轉換時,其數據的意義、轉換后的取值等都有可能發生變化,而這些細節若考慮不周,就很有可能留下隱患。
五、 宏、常量
規則5.1 用宏定義表達式時,要使用完備的括號。
說明:因為宏只是簡單的代碼替換,不會像函數一樣先將參數計算后,再傳遞。
規則5.2 將宏所定義的多條表達式放在大括號中。
說明:更好的方法是多條語句寫成do while(0)的方式。
規則5.3 使用宏時,不允許參數發生變化。
規則5.4 不允許直接使用魔鬼數字。
說明:使用魔鬼數字的弊端:代碼難以理解;如果一個有含義的數字多處使用,一旦需要修改這個數值,代價慘重。使用明確的物理狀態或物理意義的名稱能增加信息,并能提供單一的維護點。
建議5.1 除非必要,應盡可能使用函數代替宏。
說明:宏對比函數,有一些明顯的缺點:宏缺乏類型檢查,不如函數調用檢查嚴格。
建議5.2 常量建議使用const定義代替宏。
建議5.3 宏定義中盡量不使用return、goto、continue、break等改變程序流程的語句。
六、質量保證
原則6.1 代碼質量保證優先原則
(1)正確性,指程序要實現設計要求的功能。
(2)簡潔性,指程序易于理解并且易于實現。
(3)可維護性,指程序被修改的能力,包括糾錯、改進、新需求或功能規格變化的適應能力。
(4)可靠性,指程序在給定時間間隔和環境條件下,按設計要求成功運行程序的概率。
(5)代碼可測試性,指軟件發現故障并隔離、定位故障的能力,以及在一定的時間和成本前提下,進行測試設計、測試執行的能力。
(6)代碼性能高效,指是盡可能少地占用系統資源,包括內存和執行時間。
(7)可移植性,指為了在原來設計的特定環境之外運行,對系統進行修改的能力。
(8)個人表達方式/個人方便性,指個人編程習慣。
原則6.2 要時刻注意易混淆的操作符。比如說一些符號特性、計算優先級。
原則6.3 必須了解編譯系統的內存分配方式,特別是編譯系統對不同類型的變量的內存分配規則,如局部變量在何處分配、靜態變量在何處分配等。
原則6.4 不僅關注接口,同樣要關注實現。
說明:這個原則看似和“面向接口”編程思想相悖,但是實現往往會影響接口,函數所能實現的功能,除了和調用者傳遞的參數相關,往往還受制于其他隱含約束,如:物理內存的限制,網絡狀況,具體看“抽象漏洞原則”。
規則6.1 禁止內存操作越界。
堅持下列措施可以避免內存越界:
數組的大小要考慮最大情況,避免數組分配空間不夠。
避免使用危險函數sprintf /vsprintf/strcpy/strcat/gets操作字符串,使用相對安全的函數snprintf/strncpy/strncat/fgets代替。
使用memcpy/memset時一定要確保長度不要越界
字符串考慮最后的’’, 確保所有字符串是以’’結束
指針加減操作時,考慮指針類型長度
數組下標進行檢查
使用時sizeof或者strlen計算結構/字符串長度,,避免手工計算
堅持下列措施可以避免內存泄漏:
異常出口處檢查內存、定時器/文件句柄/Socket/隊列/信號量/GUI等資源是否全部釋放
刪除結構指針時,必須從底層向上層順序刪除
使用指針數組時,確保在釋放數組時,數組中的每個元素指針是否已經提前被釋放了
避免重復分配內存
小心使用有return、break語句的宏,確保前面資源已經釋放
檢查隊列中每個成員是否釋放
規則6.3 禁止引用已經釋放的內存空間。
堅持下列措施可以避免引用已經釋放的內存空間:
內存釋放后,把指針置為NULL;使用內存指針前進行非空判斷。
耦合度較強的模塊互相調用時,一定要仔細考慮其調用關系,防止已經刪除的對象被再次使用。
避免操作已發送消息的內存。
自動存儲對象的地址不應賦值給其他的在第一個對象已經停止存在后仍然保持的對象(具有更大作用域的對象或者靜態對象或者從一個函數返回的對象)
規則6.4 編程時,要防止差1錯誤。
說明:此類錯誤一般是由于把“<=”誤寫成“<”或“>=”誤寫成“>”等造成的,由此引起的后果,很多情況下是很嚴重的,所以編程時,一定要在這些地方小心。當編完程序后,應對這些操作符進行徹底檢查。使用變量時要注意其邊界值的情況。
建議6.1 函數中分配的內存,在函數退出之前要釋放。
說明:有很多函數申請內存,,保存在數據結構中,要在申請處加上注釋,說明在何處釋放。
建議6.2 if語句盡量加上else分支,對沒有else分支的語句要小心對待。
建議6.3 不要濫用goto語句。
說明:goto語句會破壞程序的結構性,所以除非確實需要,最好不使用goto語句。
建議6.4 時刻注意表達式是否會上溢、下溢。
七、 程序效率
原則7.1 在保證軟件系統的正確性、簡潔、可維護性、可靠性及可測性的前提下,提高代碼效率。
原則7.2 通過對數據結構、程序算法的優化來提高效率。
建議7.1 將不變條件的計算移到循環體外。
建議7.2 對于多維大數組,避免來回跳躍式訪問數組成員。
建議7.3 創建資源庫,以減少分配對象的開銷。
建議7.4 將多次被調用的 “小函數”改為inline函數或者宏實現。
八、 注釋
原則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 文件頭、函數頭、全局常量變量、類型定義的注釋格式采用工具可識別的格式。
說明:采用工具可識別的注釋格式,例如doxygen格式,方便工具導出注釋形成幫助文檔。
九、 排版與格式
規則9.1 程序塊采用縮進風格編寫,每級縮進為4個空格。
說明:當前各種編輯器/IDE都支持TAB鍵自動轉空格輸入,需要打開相關功能并設置相關功能。編輯器/IDE如果有顯示TAB的功能也應該打開,方便及時糾正輸入錯誤。
規則9.2 相對獨立的程序塊之間、變量說明之后必須加空行。
規則9.3 一條語句不能過長,如不能拆分需要分行寫。一行到底多少字符換行比較合適,產品可以自行確定。
換行時有如下建議:
換行時要增加一級縮進,使代碼可讀性更好;
低優先級操作符處劃分新行;換行時操作符應該也放下來,放在新行首;
換行時建議一個完整的語句放在一行,不要根據字符數斷行
規則9.4 多個短語句(包括賦值語句)不允許寫在同一行內,即一行只寫一條語句。
規則9.5 if、for、do、while、case、switch、default等語句獨占一行。
規則9.6 在兩個以上的關鍵字、變量、常量進行對等操作時,它們之間的操作符之前、之后或者前后要加空格;進行非對等操作時,如果是關系密切的立即操作符(如->),后不應加空格。
建議9.1 注釋符(包括?/??//??/?)與注釋內容之間要用一個空格進行分隔。
建議9.2 源程序中關系較為緊密的代碼應盡可能相鄰。
十、 表達式
規則10.1 表達式的值在標準所允許的任何運算次序下都應該是相同的。
建議10.1 函數調用不要作為另一個函數的參數使用,否則對于代碼的調試、閱讀都不利。
建議10.2 賦值語句不要寫在if等語句中,或者作為函數的參數使用。
建議10.3 賦值操作符不能使用在產生布爾值的表達式上。
十一、 代碼編輯、編譯
規則11.1 使用編譯器的最高告警級別,理解所有的告警,通過修改代碼而不是降低告警級別來消除所有告警。
規則11.2 在產品軟件(項目組)中,要統一編譯開關、靜態檢查選項以及相應告警清除策略。
規則11.3 本地構建工具(如PC-Lint)的配置應該和持續集成的一致。
規則11.4 使用版本控制(配置管理)系統,及時簽入通過本地構建的代碼,確保簽入的代碼不會影響構建成功。
建議11.1 要小心地使用編輯器提供的塊拷貝功能編程。
十二、 可測性
原則12.1 模塊劃分清晰,接口明確,耦合性小,有明確輸入和輸出,否則單元測試實施困難。
說明:單元測試實施依賴于:
模塊間的接口定義清楚、完整、穩定;
模塊功能的有明確的驗收條件(包括:預置條件、輸入和預期結果);
模塊內部的關鍵狀態和關鍵數據可以查詢,可以修改;
模塊原子功能的入口唯一;
模塊原子功能的出口唯一;
依賴集中處理:和模塊相關的全局變量盡量的少,或者采用某種封裝形式。
規則12.1 在同一項目組或產品組內,要有一套統一的為集成測試與系統聯調準備的調測開關及相應打印函數,并且要有詳細的說明。
規則12.2 在同一項目組或產品組內,調測打印的日志要有統一的規定。
說明:統一的調測日志記錄便于集成測試,具體包括:
統一的日志分類以及日志級別;
通過命令行、網管等方式可以配置和改變日志輸出的內容和格式;
在關鍵分支要記錄日志,日志建議不要記錄在原子函數中,否則難以定位;
調試日志記錄的內容需要包括文件名/模塊名、代碼行號、函數名、被調用函數名、錯誤碼、錯誤發生的環境等。
規則12.3 使用斷言記錄內部假設。
規則12.4 不能用斷言來檢查運行時錯誤。
說明:斷言是用來處理內部編程或設計是否符合假設;不能處理對于可能會發生的且必須處理的情況要寫防錯程序,而不是斷言。如某模塊收到其它模塊或鏈路上的消息后,要對消息的合理性進行檢查,此過程為正常的錯誤檢查,不能用斷言來實現。
建議12.1 為單元測試和系統故障注入測試準備好方法和通道。
十三、 安全性
原則13.1 對用戶輸入進行檢查。
說明:不能假定用戶輸入都是合法的,因為難以保證不存在惡意用戶,即使是合法用戶也可能由于誤用誤操作而產生非法輸入。用戶輸入通常需要經過檢驗以保證安全,特別是以下場景:
用戶輸入作為循環條件
用戶輸入作為數組下標
用戶輸入作為內存分配的尺寸參數
用戶輸入作為格式化字符串
用戶輸入作為業務數據(如作為命令執行參數、拼裝sql語句、以特定格式持久化)
這些情況下如果不對用戶數據做合法性驗證,很可能導致DOS、內存越界、格式化字符串漏洞、命令注入、SQL注入、緩沖區溢出、數據破壞等問題。
可采取以下措施對用戶輸入檢查:
用戶輸入作為數值的,做數值范圍檢查
用戶輸入是字符串的,檢查字符串長度
用戶輸入作為格式化字符串的,檢查關鍵字“%”
用戶輸入作為業務數據,對關鍵字進行檢查、轉義
規則13.1 確保所有字符串是以NULL結束。
說明: C語言中??作為字符串的結束符,即NULL結束符。標準字符串處理函數(如strcpy()、 strlen())
依賴NULL結束符來確定字符串的長度。沒有正確使用NULL結束字符串會導致緩沖區溢出和其它未定義的行為。
為了避免緩沖區溢出,常常會用相對安全的限制字符數量的字符串操作函數代替一些危險函數。如:
用strncpy()代替strcpy()
用strncat()代替strcat()
用snprintf()代替sprintf()
用fgets()代替gets()
這些函數會截斷超出指定限制的字符串,但是要注意它們并不能保證目標字符串總是以NULL結尾。如果源字符串的前n個字符中不存在NULL字符,目標字符串就不是以NULL結尾。
規則13.2 不要將邊界不明確的字符串寫到固定長度的數組中。
說明:邊界不明確的字符串(如來自gets()、getenv()、scanf()的字符串),長度可能大于目標數組長度,直接拷貝到固定長度的數組中容易導致緩沖區溢出。
規則13.3 避免整數溢出。
說明:當一個整數被增加超過其最大值時會發生整數上溢,被減小小于其最小值時會發生整數下溢。帶符號和無符號的數都有可能發生溢出。
規則13.4 避免符號錯誤。
說明:有時從帶符號整型轉換到無符號整型會發生符號錯誤,符號錯誤并不丟失數據,但數據失去了原來的含義。
帶符號整型轉換到無符號整型,最高位(high-order bit)會喪失其作為符號位的功能。如果該帶符號整數的值非負,那么轉換后值不變;如果該帶符號整數的值為負,那么轉換后的結果通常是一個非常大的正數。
規則13.5:避免截斷錯誤。
說明:將一個較大整型轉換為較小整型,并且該數的原值超出較小類型的表示范圍,就會發生截斷錯誤,原值的低位被保留而高位被丟棄。截斷錯誤會引起數據丟失。使用截斷后的變量進行內存操作,很可能會引發問題。
規則13.6:確保格式字符和參數匹配。
說明:使用格式化字符串應該小心,確保格式字符和參數之間的匹配,保留數量和數據類型。格式字符和參數之間的不匹配會導致未定義的行為。大多數情況下,不正確的格式化字符串會導致程序異常終止。
規則13.7 避免將用戶輸入作為格式化字符串的一部分或者全部。
說明:調用格式化I/O函數時,不要直接或者間接將用戶輸入作為格式化字符串的一部分或者全部。攻擊者對一個格式化字符串擁有部分或完全控制,存在以下風險:進程崩潰、查看棧的內容、改寫內存、甚至執行任意代碼。
規則13.8 避免使用strlen()計算二進制數據的長度。
說明:strlen()函數用于計算字符串的長度,它返回字符串中第一個NULL結束符之前的字符的數量。因此用strlen()處理文件I/O函數讀取的內容時要小心,因為這些內容可能是二進制也可能是文本。
規則13.9 使用int類型變量來接受字符I/O函數的返回值。
規則13.10 防止命令注入。
說明:C99函數system()通過調用一個系統定義的命令解析器(如UNIX的shell,Windows的CMD.exe)來執行一個指定的程序/命令。類似的還有POSIX的函數popen()。
十四、 單元測試
規則14.1 在編寫代碼的同時,或者編寫代碼前,編寫單元測試用例驗證軟件設計/編碼的正確。
建議14.1 單元測試關注單元的行為而不是實現,避免針對函數的測試。
說明:應該將被測單元看做一個被測的整體,根據實際資源、進度和質量風險,權衡代碼覆蓋、打樁工作量、補充測試用例的難度、被測對象的穩定程度等,一般情況下建議關注模塊/組件的測試,盡量避免針對函數的測試。盡管有時候單個用例只能專注于對某個具體函數的測試,但我們關注的應該是函數的行為而不是其具體實現細節。
十五、 可移植性
規則15.1 不能定義、重定義或取消定義標準庫/平臺中保留的標識符、宏和函數。
建議15.1 不使用與硬件或操作系統關系很大的語句,而使用建議的標準語句,以提高軟件的可移植性和可重用性。
說明:程序中嵌入式匯編,一般都對可移植性有較大的影響。
編輯:黃飛
評論
查看更多