3.5 格式檢查
PC-Lint會檢查printf和scanf(及其家族)中的格式衝突,例如:
printf( "%+c", ... )
將產生566警示,因為加號只在數字轉換時有用,有超過一百個這樣的組合會產生警示,編譯器通常不標記這些矛盾,其他的警示還有對壞的格式的抱怨,它們是557和567。我們遵循ANSI C建立的規則,可能更重要的是我們還對大小不正確的格式進行標記(包括警示558, 559, 560 和 561)。比如 %d 格式,允許使用int和unsigned int,但是不支援double和long(如果long比int長),同樣,scanf需要參數指向的對象大小正確。如果只是參數的類型(不是大小)與格式不一致,那將產生626和627警示。-printf 和 -scanf選項允許使用者指定與printf或scanf函數族類似的函數,-printf_code 和 -scanf_code也可以被用來描述非標準的 % 碼。
3.6 縮排檢查
根據代碼中的縮排問題,PC-Lint也會產生相應的警示,因為縮排的問題有很大一部分是由於代碼結構不良或者大括弧的遺漏造成的。比如下面的例子:
if( ... )
if( ... )
statement
else statement
很明顯這裡的else是和第一個if語句對應的,而在這裡編譯器則把它和第二個if對應起來。PC-Lint會對這種情況產生警示。和這樣的縮排檢查相關的警示主要有三個725(no positive indentation)、525(negatively indented from)、539(Did not expect positive indentation from Location)要進行縮排檢查,我們首先要設定檔案中的tab鍵所對應的空格數,預設的是佔用8個空格,這個參數可以用-t#選項進行修改。比如-t4表示tab鍵佔用4個空格長度。另外,縮排檢查還和代碼的編碼格式策略相關,需要進行必要的調整。
3.7 const變數檢查
對於const變數的檢查,PC-Lint是完全支援的。使用const變數,對於提高代碼的品質非常有好處,看一下下面的例子:
char *strcpy( char *, const char * );
const char c = 'a';
const char *p = &c;
void main()
{
char buf[100];
c = 'b';
*p = 'c';
strcpy( p, buf );
...
這裡的c和*P指向的內容都是靜態變數,不可修改。上面的代碼明顯違反了這個規定,會產生Error(11),另外,把P作為第一個參數傳入strcpy中,會產生警示605(Increase in pointer capability),而把buf作為第二個參數傳入strcpy函數中,會產生警示603(Symbol 'Symbol' (Location) not initialized),因為buf沒有初始化,而作為靜態變數的第二個參數,是不能在strcpy函數中再被初始化的。
3.8 volatile變數檢查
對於volatile變數的檢查,在PC-Lint中有這樣的規定,如果一個運算式中同時使用了兩次相同的volatile變數,那麼就會給出564警示,因為這時候會產生賦值順序的問題。
volatile char *p;
volatile char f();
n = (f() << 8) | f(); /* Warning 564 */
n = (*p << 8) | *p; /* Warning 564 */
四 PC-Lint軟體使用方法
4.1 安裝與配置
PC-lint軟體性價比高,易於學習,容易推廣和固化到軟體開發測試流程中去,所以在全世界得到了廣泛的應用。PC-lint使用方法很簡單,可以用命令列方式進行,例如lint-nt –u std.lnt test1.c test2.c test3.c 也可以使用MAKEFILE的方式。此外,它還可以整合到很多開發環境或常用的代碼編輯軟體中,比如整合到Source Insight/SLICKEDIT/MS VC6.0/KEIL C..等。PC-Lint還支援Scott Meyes的名著(Effective C++/More Effective C++)中說描述的各種提高效率和防止錯誤的方法。
PC-lint的安裝非常簡單,以PC-lint 8.0為例,運行安裝程式將其釋放到指定的安裝目錄即可,比如c:/pclint8。然後需要運行PC-lint的組態工具config.exe產生選項和檢查設定檔,以剛才的安裝路徑為例,config.exe應該位於:C:/pclint8/config.exe。設定檔是代碼檢查的依據,PC-lint內建了一個標準設定檔std.lnt,但是這個檔案沒有目錄包含資訊(標頭檔目錄),通常對代碼檢查的時候都需要指定一些特殊的包含目錄,所以要在標準配置的基礎上產生針對某個項目代碼檢查的定製配置。下面就以Microsoft Visual C++ 6的開發環境為例,介紹一下定製配置的過程。
運行C:/pclint8/config.exe後出現一個歡迎介面,提示著作權資訊,4.1所示:
圖4.1 配置歡迎視窗
點擊“下一步”按鈕出現pc-lint.exe命令列使用說明視窗(圖4.2所示):
圖4.2 pc-lint.exe命令列使用說明視窗
點擊“下一步”按鈕繼續,接著是選擇建立或修改已有設定檔STD.LNT的選項:
圖4.3 選擇如何使用設定檔STD.LNT
因為我們是第一次配置,所以選擇上面一個選項“Create a new STD.LNT”,這樣做不會修改已有設定檔STD.LNT的內容,而是建立一個新的STD_x.LNT檔案,檔案名稱中的x是從“a”到“z”26個英文字元中的任意一個,一般是按順序排列,從“a”開始。STD_x.LNT檔案的內容被初始化為STD.LNT內容的拷貝。4.3所示,使用預設的PC-Lint路徑,然後點擊“下一步”按鈕選擇編譯器:
圖4.4 選擇編譯器
接下來是選擇編譯器,在下拉框中選擇自己使用的編譯器。這裡我們選擇“Microsoft Visual C++ 6.x (co-msc60.lnt)”。如果沒有自己使用的編譯器,可選擇通用編譯器“Generic Compilers”。這個選項會體現在co-xxx.lnt檔案中,並存放在前面我們選擇的配置路徑(C:/PCLint8)下,在後面配置選項我們所選擇的***.LNT均會被存放到這個路徑下。點擊“下一步”按鈕選擇記憶體模式:
圖4.5 選擇記憶體模式
可以根據自己程式區和資料區的實際大小選擇一個恰當的記憶體模型,記憶體模型的選項會體現在STD.LNT檔案或新建立的STD_x.LNT中。因為我們的開發環境是32位的Windows,所以選擇“32-bit Flat Model”,然後點擊“下一步”按鈕選擇所要的支援庫的配置資訊:
圖4.6 選擇軟體庫的配置資訊
PC-Lint對現在常用的一些軟體庫都提供了定製的配置資訊,選擇這些定製資訊有助於開發人員將錯誤或資訊的注意力集中在自己的代碼中,選擇的支援庫配置將被引入到STD.LNT檔案或新建立的STD_x.LNT檔案中。選擇常用的ATL、MFC、STL等配置,然後點擊“下一步”按鈕:
圖4.7 選擇軟體名人的編程建議
這是一個比較有意思的選項,就是讓你選擇是否支援為使用C/C++編程提出過重要建議的作者的一些關於編程方面的個人意見。如果選擇某作者的建議,那麼他提出的編程建議方面的選項將被開啟,作者建議的配置名為AU-xxx.LNT,建議全部選擇,然後點擊“下一步”按鈕:
圖4.8 選擇是否現在設定包含檔案目錄
接下來是選擇用何種方式設定包含檔案目錄,如果選擇使用-i方式協助設定包含檔案選項,下一步就會要求輸入一個或多個包含路徑。也可以跳過這一步,以後手工修改設定檔,-i選項體現在STD.LNT檔案或新建立的STD_x.LNT檔案中,每個目錄前以-i引導,目錄間以空格分隔,如果目錄名中有長檔名或包含空格,使用時要加上雙引號,如-i“E:/Program Files/Microsoft Visual C++/VC98/Indlue”。這裡我們選擇用-i方式協助我們來設定,然後點擊“下一步”按鈕:
圖4.9 選擇是否現在設定包含檔案目錄
這一步就是在下面的文字框裡可手工輸入檔案包含路徑,用分號“;”或用ctrl+Enter換行來分割多個包含路徑,或者可以點中Brows,在分類樹中直接選擇。填完後點擊“下一步”按鈕:
圖4.10 提示std_x.lnt已經被建立
因為第三步選擇了“Create a new STD.LNT”選項,所以出現以下對話方塊,表示std_x.lnt,std.lnt在配置路徑下已被建立,這裡的std_a.lnt實際上包含了std.lnt的資訊,除此之外還有我們選擇的包含路徑和庫配置資訊。單擊“確定”按鈕繼續:
圖4.11 提示是否為其它編譯環境建立設定檔
選擇“確定”後,會接著提示是否為其它編譯環境建立設定檔,如果選擇“是”將從第四步開始建立一個新的設定檔。這裡我們選擇“否”:
圖4.12 是否替換std.lnt檔案
接下來會提示是否使用現在產生的std_x.lnt檔案取代std.lnt檔案。如果選擇“是”將會用std_x.lnt檔案的內容覆蓋std.lnt檔案的內容,使得當前建立的配置選項成為以後建立新的設定檔時的預設配置。通常我們選擇“否”繼續下一步:
圖4.13 產生全域代碼檢查選項檔案OPTIONS.LNT
接下來將會準備產生一個控制全域編譯資訊顯示情況的選項檔案OPTIONS.LNT,該檔案的產生方式有兩種,一種是安裝程式對幾個核心選項逐一解釋並提問你是否取消該選項,如果你選擇取消,則會體現在OPTIONS.LNT檔案中,具體體現方式是在該類資訊編碼前加-e,後面有一系列逐一選擇核心選項的過程。如果選擇第二種選擇方式,安裝檔案會先產生一個空的OPTIONS.LNT檔案,等你以後在實際應用時加入必要的選項。這裡選擇“No”選項,即不取消這些選項,然後單擊“下一步”:
圖4.14 選擇所支援的整合式開發環境
接著選擇所支援的整合式開發環境選項,可選多個或一個也不選,PC-Lint提供了整合在多種開發環境中工作的功能,例如可整合在VC、BC、Source Insight中。這裡我們選擇Microsift Visual C++ 6.0,這樣env-v6.lnt就會被拷貝到配置路徑中。然後單擊“下一步”:
圖4.15 選擇LIN.BAT檔案的使用方式
安裝程式會產生一個LIN.BAT檔案,該檔案是運行PC-Lint的批次檔,為了使該檔案能在任何路徑下運行,安裝程式提供了兩種方法供你選擇。第一種方法是讓你選擇把LIN.BAT拷貝到任何一個PATH目錄下。第二種方法是產生一個LSET.BAT檔案,在每次使用PC-LINT前先運行它來設定路徑,或者把LSET.BAT檔案的內容拷貝到AUTOEXEC.BAT檔案中。建議選擇第一種方法,指定的目錄為當前PC-Lint的安裝目錄。我們選擇第一種方式:“copy LIN.BAT to one of my PATH directory”,然後單擊“下一步”輸入PATH目錄:
圖4.16 指定PATH目錄
輸入安裝目錄C:/PCLint8作為PATH目錄,然後單擊“下一步”按鈕進入最後的確認視窗:
圖4.17 確認完成配置
到此就完成了PC-Lint的安裝配置工作,單擊“完成”按鈕就可以使用PC-Lint了。以上配置過程中在配置路徑下產生的多個*.lnt檔案,除了std.lnt、std_x.lnt和option.lnt為設定精靈所產生,其它co-xxx.lnt、lib-xxx.lnt、env-xxx.lnt均是從原始安裝目錄中拷貝出來的,在這個目錄下還有其它PCLint所支援的編譯器、庫及整合式開發環境的lnt設定檔,所有的lnt檔案均為文字檔。
上面的配置方法適合於剛開始接觸PC-lint時使用,對於熟練的使用者可以直接編輯、編寫各*.lnt設定檔安成上面的配置工作,或者定製出更適合自己使用的配置環境。
4.2 PC-Lint與常用開發工具的整合
PC-Lint的使用方法很簡單,可以用命令列方式進行,也可以整合到開發環境中,下面就分別介紹這些用法
4.2.1 使用命令列方式
命令列的使用方式是PC-lint最基本的使用方式,也是其他各種整合使用方式的基礎,通過命令列可以完成PC-lint的全部程式碼分析工作。PC-lint的命令列有下列形式:
Lint-nt option file1 [file1 file3 …]
其中的Lint-nt是PC-lint在Windows NT/2000/XP平台上的可執行程式Lint-nt.exe,它完成PC-lint的準系統;option代表PC-lint可接受的各種選項,這是PC-lint最為複雜的部分,它的選項有300多種,可以分為:錯誤資訊禁止選項、變數類型大小選項、冗餘資訊選項、標誌選項、輸出格式選項和其他選項等幾類,這些選項在本文的第三部分已經介紹過了;file為待檢查的源檔案。
另外值得注意的一點是,在命令列中可以加入前面提到的*.lnt設定檔名,並可以把它看作是命令列的擴充,其中配置的各種選項和檔案清單,就和寫在命令列中具有一樣的效果。
4.2.2 PC-Lint與Visual C++整合式開發環境(IDE)整合
在所有整合式開發環境中,PC-Lint 8.0對VC++6和VC++7.0的支援是最完善的,甚至支援直接從VC的工程檔案(VC6是*.dsp,VC7是*.vcproj)匯出對應工程的.Lnt檔案,此檔案包含了工程設定中的先行編譯宏,標頭檔包含路徑,源檔案名稱,無需人工編寫工程的.Lnt檔案。
PC-Lint與VC整合的方式就是在VC的整合式開發環境中添加幾個定製的命令,添加定製命令的方法是選擇“Tools”的“Customize...”命令,在彈出的Customize視窗中選擇“Tools”標籤,在定製工具命令的標籤頁中添加定製命令。首先要為VC的整合式開發環境添加一個匯出當前工程的.Lnt設定檔的功能,匯出.Lnt檔案的命令列是:
lint-nt.exe +linebuf $(TargetName).dsp>$(TargetName).lnt
參數+linebuf表示加倍行緩衝的大小,最初是600 bytes,行緩衝用於存放當前行和你讀到的最長行的資訊。$(TargetName)是VC整合式開發環境的環境變數,表示當前啟用的Project名字,注意要選中“Use Output Window”選項,這樣PC-Lint就會將資訊輸出到Output視窗中。填寫效果4.18所示:
圖4.18 添加匯出項目.Lnt檔案的定製命令
接著添加一個檢查當前檔案的定製命令,檢查檔案的命令列為:
lint-nt.exe -i"C:/PCLint8" -u std_g.lnt env-vc6.lnt "$(FileName)$(FileExt)"
第一個參數-i"C:/PCLint8"為PC-Lint搜尋*.lnt檔案的目錄,這裡就是我們的配置路徑。std_g.lnt是為VC編譯環境定製的設定檔,$(FileName)和$(FileExt)是VC整合式開發環境的環境變數,"$(FileName)$(FileExt)"表示當前檔案的檔案名稱。和匯出.Lnt命令一樣,這個命令也要使用VCIntegration Environment的Output視窗輸出檢查資訊,所以要選中“Use Output Window”選項,4.19所示:
圖4.19 添加檢查當前檔案檔案的定製命令
最後要添加一個檢查整個工程的定製命令,檢查整個工程的命令列是:
lint-nt.exe +ffn -i"C:/PCLint8" std_g.lnt env-vc6.lnt $(TargetName).lnt>$(TargetName).chk
這個命令的結果就是將整個工程的檢查結果輸出到與工程同名的.chk檔案中。參數中+ffn表示Full File Names,可被用於控制是否使用的完整路徑名稱表示。
下面就以一個簡單的例子介紹一下如何在VC整合式開發環境中使用PC-Lint。首先建立一個“Win32 Console Application”類型的簡單工程(輸出“Hello World”),然後將本文第二章引用的例子代碼添加到工程的代碼中,最後將這個工程代碼所倚賴的包含目錄手工添加到設定檔中,因為代碼檢查要搜尋stdafx.h這個先行編譯檔案,所以本例要手工添加工程代碼所在的目錄。本文的例子產生的設定檔是std_g.lnt,用文字檔開啟std_g.lnt,在檔案中添加一行:
-iC:/unzipped/test
“C:/unzipped/test”就是例子工程所在的目錄(stdafx.h就在這個目錄)。如果你的工程比較龐大,有很多標頭檔包含目錄,就需要將這些目錄一一添加到設定檔。在確保代碼輸入沒有錯誤之後(有錯誤頁沒關係,PC-Lint會檢查出錯誤),就可以開始代碼檢查了。例子工程,開啟要檢查的代碼檔案,本例是test.cpp,然後選擇“Tools”菜單下的“PC_LINT 8.0 Check Current File”命令,Output視窗輸出對本檔案的檢查結果,4.20所示:
圖4.20 檢查結果
4.2.3 PC-Lint與source insight整合
PC-Lint與source insight的整合也是通過添加定製命令實現的,從“Options”菜單中選擇“Custom Commands”命令項。點擊“Add…”按鈕,4.21所示,在彈出的“Custom Commands”視窗中完成以下輸入:
在Name欄中輸入“PC-lint Check Current File”,原則上這個名稱可以隨便起,只要你能搞清楚它的含義就可以了;
在Run欄中輸入“C:/PcLint/lint-nt -u -iC:/PcLint/Lint std_f env-si %f”其中C:/PcLint是你PC-LINT的安裝目錄,std_f表示為Source Insight定製的設定檔std_f.lnt;
在Output欄中選擇“Iconic Window”、“Capture Output”選項;
在Control欄中選擇“Save Files First”;
在Source Links in Output欄中選擇“Parse Links in Output”、“File,then Line”;
在Pattern欄中輸入“^/([^ ]*/) /([0-9]+/)”;
圖4.21 在Source Insight中添加定製命令
命令添加完成後就可以點擊“Run”按鈕就可以對當前檔案執行PC-Lint檢查。為了方便使用,還可以點擊“Menu...”按鈕將這個定製命令添加到Source Insight的菜單中。
4.2.4 PC-Lint與UltraEdit整合
在UltraEdit中整合PC-Lint的方法和Source Insight類似,也是添加一個定製命令菜單,具體實現方法是先單擊UltraEdit的“進階”菜單中的“工具配置”命令,4.22所示,在開啟的配置視窗中依次輸入以下內容:
在“功能表項目名”欄輸入“PC-lint Check Current File”;
在“命令列”欄輸入以下命令:C:/PCLint/lint-nt –u -iC:/PCLint std env-si %f 其中,C:/PCLint是PC-Lint的安裝目錄,使用std.lnt中的配置,由於UltraEdit和Source Insightde 的檢查環境類似,所以借用env-si中的環境配置;
在“工作目錄”欄輸入以下路徑:E:/code,這是代碼所在目錄;
選中“先儲存所有檔案”選項;
在“命令輸出”欄中,選中“輸出到列表”和“捕捉輸出”兩個選項;
點“插入”將命令列插入UltraEdit的菜單中;
圖4.22 在UltraEdit中添加定製命令
此時在UltraEdit的“進階”菜單中會增加一個“PC-lint Check Current File”菜單,點擊該菜單即可對當前檔案執行PC-lint檢查。
五 總結
軟體除錯是軟體項目開發成本和延誤的主要因素,PC-lint能夠幫你在程式動態測試之前發現編碼錯誤,降低軟體消除錯誤的成本。使用PC-Lint在代碼走讀和單元測試之前進行檢查,可以提前發現程式隱藏錯誤,提高代碼品質,節省測試時間。另外,使用PC-lint的編碼規則檢查,可以有效地規範軟體人員的編碼行為。如果能夠在軟體開發過程中有效地使用PC-lint代碼檢查工具,將大大地提高代碼品質,降低軟體成本。
參考文獻
[1] Gimpel Software. Reference Manual for PC-lint/FlexeLint. July,2001
[2] PC-Lint選項詳解
附錄一 PC-Lint 重要檔案說明
Msg.txt :解釋警示的內容。
options.lnt :反映全域編譯資訊顯示情況的選項檔案,通常需要添加自定選項以使代碼檢查更為嚴格。
env-xx.lnt :講述如何將PC-lint與對應的編輯環境結合起來,xx是si表示是為Source Insight配置的檢查環境,xx是vc6則表示是為Visual C++ 6.0準備的檢查環境。
co-xxx.lnt :選定的編譯器與庫選項。
std.lnt :標準設定檔,包含記憶體模型等全域性東西。
lib-xxx.lnt :庫類型的列表,包括標準C/C++庫,MFC庫,OWL庫等等。
au-xxx.LNT :C++編程提出過重要建議的作者,選擇某作者後,他提出的編程建議方面的選項將被開啟。
附錄 二 錯誤資訊禁止選項說明
命令格式 說明 代碼中的舉例
-e# 隱藏某類錯誤 /*lint -e725 */
-e(#) 隱藏下一運算式中的某類錯誤 /*lint –e(534) */
printf(“it’s all”);
!e# 隱藏本行中的錯誤 /*lint !e534*/ printf(“it’s all”);
-esym(#, Symbol) 隱藏有關某符號的錯誤 /*lint –esym(534, printf)*/
printf(“it’s all”);
-elib(#) 隱藏標頭檔中的某類錯誤 /*lint –elib(129) */
#include “r01.h”
-efunc(#, <func>) 隱藏某個函數中的特定錯誤 /*lint –efunc(534, mchRelAll)*/
unsigned int mchRelAll(mchHoData
*pHoData)
{
printf(“it’s all”);
}
附錄 三 PC-Lint檢測中的常見錯誤
錯誤編碼 錯誤說明 舉例
40 變數未聲明
506 固定的Boolean值 char c=3;
if(c<300){}
525 縮排格式錯誤
527 無法執行到的語句 if(a > B)
return TRUE;
else
return FALSE;
return FALSE;
529 變數未引用 檢查變數未引用的原因
530 使用未初始化的變數
534 忽略函數傳回值
539 縮排格式錯誤
545 對陣列變數使用& char arr[100], *p;
p=&arr;
603 指標未初始化 void print_str(const char *p);
…
char *sz;
print_str(sz);
605 指標能力增強 void write_str(char *lpsz);
…
write_str(“string”);
613 可能使用了null 指標
616 在switch語句中未使用break;
650 比較數值時,常量的範圍超過了 if( ch == 0xFF ) ...
變數範圍
713 把有符號型數值賦給了無符號型
數值
715 變數未引用
725 Indentation錯誤
734 在賦值時發生變數越界 int a, b, c;
…
c=a*b;
737 無符號型變/常量和有變數型
變/常量存在於同一個運算式中。
744 在switch語句中沒有default
752 本地聲明的函數未被使用
762 函數重複聲明
774 Boolean運算式始終返回真/假 char c;
if(c < 300)