by WANG
C/C++語言的語法擁有其它語言所沒有的靈活性,這種靈活性帶來了代碼效率的提升,但相應(yīng)也使得代碼編寫具有很大的隨意性,另外C/C++編譯器不進(jìn)行強(qiáng)制類型檢查,也不做任何邊界檢查,這就增加了代碼中存在隱患的可能性。如果能夠在代碼提交測試之前發(fā)現(xiàn)這些潛在的錯(cuò)誤,就能夠極大地減輕測試人員的壓力,減少軟件項(xiàng)目的除錯(cuò)成本,可是傳統(tǒng)的C/C++編譯器對此已經(jīng)無能為力,這個(gè)任務(wù)只能由專用的代碼檢查工具完成,PC-Lint因此應(yīng)運(yùn)而生。
PC-Lint是GIMPEL SOFTWARE公司開發(fā)的C/C++軟件代碼靜態(tài)分析工具,它的全稱是PC-Lint for C/C++,PC-Lint能夠在Windows、MS-DOS和OS/2平臺上使用,以二進(jìn)制可執(zhí)行文件的形式發(fā)布。PC-lint在全球擁有廣泛的客戶群,許多大型的軟件開發(fā)組織都把PC-Lint檢查作為代碼走查的第一道工序。 PC-Lint不僅能夠?qū)Τ绦蜻M(jìn)行全局分析,識別沒有被適當(dāng)檢驗(yàn)的數(shù)組下標(biāo),報(bào)告未被初始化的變量,警告使用空指針以及冗余的代碼,還能夠有效地幫你提出許多程序在空間利用、運(yùn)行效率上的改進(jìn)點(diǎn)。
通過下面的例子就可以看出PC-Lint工具的強(qiáng)大功能:
1:
2:char *report( int m, int n, char *p )
3:{
4: int result;
5: char *temp;
6: long nm;
7: int i, k, kk;
8: char name[11] = "Joe Jakeson";
9:
10: nm = n * m;
11: temp = p == "" ? "null" : p;
12: for( i = 0; i<m; I++ ) {
14: k++;
15: kk = i;
16: }
17:
18: if( k== 1 ) result = nm;
19: else if( kk > 0 ) result = 1;
20: else if( kk < 0 ) result = -1;
21:
22: if( m == result ) return( temp );
23: else return( name );
24:}
這是一段C代碼,可以通過大多數(shù)常見的C語言編譯器的檢查,但是PC-Lint能夠發(fā)現(xiàn)其中的錯(cuò)誤和潛在的問題:第8行向name數(shù)組賦值時(shí)丟掉了結(jié)尾的null字符,第10行的乘法精度會失準(zhǔn),即使考慮到long比int的字長更長,由于符號位的原因仍然會造成精度失準(zhǔn),第11行的比較有問題,第14行的變量k沒有初始化,第15行的kk可能沒有被初始化,第22行的result也有可能沒有被初始化,第23行返回的 是一個(gè)局部對象的地址。
對于一個(gè)小程序,多數(shù)程序員都能夠及時(shí)發(fā)現(xiàn)上面出現(xiàn)的錯(cuò)誤,但是從一個(gè)擁有成千上萬行代碼的大型軟件中找出這些瑕疵將是一項(xiàng)煩瑣的工作,而且沒有人可以保證能找出所有的這類問題。如果使用PC-Lint,只需通過一次簡單的編譯就可以檢查出這些錯(cuò)誤,這將節(jié)省了大量的開發(fā)時(shí)間。從某種意義上說。PC- Lint是一種更加嚴(yán)格的編譯器,它除了可以檢查出一般的語法錯(cuò)誤外,還可以檢查出那些雖然符合語法要求,但很可能是潛在的、不易發(fā)現(xiàn)的錯(cuò)誤。
PC-Lint能夠檢查出很多語法錯(cuò)誤和語法上正確的邏輯錯(cuò)誤,PC-Lint為大部分錯(cuò)誤消息都分配了一個(gè)錯(cuò)誤號,編號小于1000的錯(cuò)誤號是分配給C 語言的,編號大于1000的錯(cuò)誤號則用來說明C++的錯(cuò)誤消息。下表列出了PC-Lint告警消息的詳細(xì)分類:
錯(cuò)誤說明 | C |
語法錯(cuò)誤 | 1-199 |
內(nèi)部錯(cuò)誤 | 200-299 |
致命錯(cuò)誤 | 300-399 |
告警 | 400-699 |
消息 | 700-800 |
可選信息 | 900-999 |
以 C語言為例,其中的編號1-199指的是一般編譯器也會產(chǎn)生的語法錯(cuò)誤;編號200-299是PC-Lint程序內(nèi)部的錯(cuò)誤,這類錯(cuò)誤不會出現(xiàn)在代碼中的;編號300-399指的是由于內(nèi)存限制等導(dǎo)致的系統(tǒng)致命錯(cuò)誤。編號400-999中出現(xiàn)的提示信息,是根據(jù)隱藏代碼問題的可能性進(jìn)行分類的:其中編號400-699指的是被檢查代碼中很可能存在問題而產(chǎn)生的告警信息;編號700-899中出現(xiàn)的信息,產(chǎn)生錯(cuò)誤的可能性相比告警信息來說級別要低, 但仍然可能是因?yàn)榇a問題導(dǎo)致的問題。編號900-999是可選信息,他們不會被默認(rèn)檢查,除非你在選項(xiàng)中指定檢查他們。
PC-Lint提供了和許多編譯器類似的告警級別設(shè)置選項(xiàng)-wLevel和處理函數(shù)庫的頭文件的告警級別-wlib(Level),分為以下幾個(gè)級別,缺省告警級別為3級:
-w0 不產(chǎn)生信息(除了遇到致命的錯(cuò)誤)
-w1 只生成錯(cuò)誤信息 -- 沒有告警信息和其它提示信息
-w2 只有錯(cuò)誤和告警信息
-w3 生成錯(cuò)誤、告警和其它提示信息(這是默認(rèn)設(shè)置)
-w4 生成所有信息
PC-Lint的檢查分很多種類,有強(qiáng)類型檢查、變量值跟蹤、語義信息、賦值順序檢查、弱定義檢查、格式檢查、縮進(jìn)檢查、const變量檢查和 volatile變量檢查等等。
下面將分別介紹PC-Lint常用的,也是比較重要的代碼檢查類型,并舉例介紹了各個(gè)檢查類型下可能出現(xiàn)的告警信息以及常用選項(xiàng)的用法:
強(qiáng)類型檢查選項(xiàng)“-strong”和它的輔助(補(bǔ)充)選項(xiàng)“-index”可以對typedef定義的數(shù)據(jù)類型進(jìn)行強(qiáng)類型檢查,以保證只有相同類型之間的變量才能互相賦值。
3.2 變量值跟蹤。
變量值初始化跟蹤技術(shù)主要是對變量的值的初始化過程進(jìn)行跟蹤,和其相關(guān)的LINT消息主要是644, 645 ("變量可能沒有初始化"), 771, 772 ("不可靠的初始化"), 530 ("未初始化的"), 和1401 - 1403 ("成員 ... 未初始化")。
變量值跟蹤技術(shù)從賦值語句、初始化和條件語句中收集信息,而函數(shù)的參數(shù)被默認(rèn)為在正確的范圍內(nèi),只有在從函數(shù)中可以收集到的信息與此不符的情況下才產(chǎn)生告警。與變量值跟蹤相關(guān)的消息有:
(1) 訪問地址越界消息(消息415,661,796)
(2) 被0除消息(54,414,795)
(3) NULL指針的錯(cuò)誤使用(413,613,794)
(4) 非法指針的創(chuàng)建錯(cuò)誤(416,662,797)
(5) 冗余的布爾值測試(774)
在某些情況下,雖然根據(jù)代碼我們可以知道確切的值,但是PC-Lint卻無法獲取所有情況下變量的值的范圍,這時(shí)候會產(chǎn)生一些錯(cuò)誤的告警信息,我們可以使用assert語句增加變量取值范圍信息的方法,來抑制這些錯(cuò)誤的告警信息的產(chǎn)生。
PC-Lint的函數(shù)值跟蹤功能會跟蹤那些將要傳遞給函數(shù)(作為函數(shù)參數(shù))變量值,當(dāng)發(fā)生函數(shù)調(diào)用時(shí),這些值被用來初始化函數(shù)參數(shù)。這種跟蹤功能被用來測定返回值,記錄額外的函數(shù)調(diào)用,當(dāng)然還可以用來偵測錯(cuò)誤。
當(dāng)一個(gè)表達(dá)式的值依賴于賦值的順序的時(shí)候,會產(chǎn)生告警564。這是C/C++語言中非常普遍的一個(gè)問題,但是很少有編譯器會分析這種情況。比如
n++ + n
這個(gè)語句是有歧義的,當(dāng)左邊的+操作先執(zhí)行的話,它的值會比右邊的先執(zhí)行的值大一,更普遍的例子是這樣的:
a[i] = i++;
f( i++, n + i );
第一個(gè)例子,看起來好像自加操作應(yīng)該在數(shù)組索引計(jì)算以后執(zhí)行,但是如果右邊的賦值操作是在左邊賦值操作之前執(zhí)行的話,那么自加一操作就會在數(shù)組索引計(jì)算之前 執(zhí)行。雖然,賦值操作看起來應(yīng)該指明一種操作順序,但實(shí)際上是沒有的。第二個(gè)例子是有歧義的,是因?yàn)楹瘮?shù)的參數(shù)值的計(jì)算順序也是沒有保證的。能保證賦值順序的操作符是布爾與(&&)或(||)和條件賦值(? :)以及逗號(,)。
這里的弱定義包含是以下內(nèi)容:宏定義、typedef名字、聲明、結(jié)構(gòu)、聯(lián)合和枚舉類型。因?yàn)檫@些東西可能在模塊中被過多定義且不被使用,PC-Lint 有很多消息用來檢查這些問題。PC-Lint的消息749-769 和1749-1769都是保留用來作為弱定義提示的。
(1) 當(dāng)一個(gè)文件#include的頭文件中沒有任何引用被該文件使用,PC-Lint會發(fā)出766告警。
(2) 為了避免一個(gè)頭文件變得過于大而臃腫,防止其中存在冗余的聲明,當(dāng)一個(gè)頭文件中的對象聲明沒有被外部模塊引用到時(shí),PC-Lint會發(fā)出759告警。
(3) 當(dāng)變量或者函數(shù)只在模塊內(nèi)部使用的時(shí)候,PC-Lint會產(chǎn)生765告警,來提示該變量或者函數(shù)應(yīng)該被聲明為static。