揭開PC-Lint9的神祕面紗


前言

    今天,又定位了一個令人懊惱的C++內存使用異常問題,最終結果,竟然是減少接口類的方法後,爲了避免編譯錯誤,順手添加的強制類型轉換導致的。
    對於這樣的問題,我們碰到很多很多次了。沒有這樣的問題,我們就不會有那麼多的攻關,那麼多的熬夜,進度也許不再那麼捉摸不透......
    我們有很多的抱怨,用的C/C++語言太底層,使用高級語言C#/JAVA等就不會有頭痛的內存問題了,而且新的語言在很多方面提高了安全性,不會有那麼多的陷阱。
    我們有很多的理由,進度太緊,如果我們多點單元測試的時間,也許不會把這樣的問題留到後面集成測試才發現;如果我們有一個安全編碼的Checklist時刻提醒着,也許就會少犯一點錯。
    ......   
    再多的抱怨和理由,都不能解決問題。我們可以從這些問題去學到經驗,去完善我們的Checklist;我們可以更加確信前期開發者測試的投入(包括TDD)在提高全流程效率方面的作用不可小視,從而加大前期的投入;但我們也需要一種手段,在必要的時候,幫我們排除一些錯誤,避免懊惱、沮喪的疑難問題定位,那就是靜態/動態代碼檢查工具。(動態檢查工具必須在能運行的情況下使用,所以先不考慮)
    C/C++的靜態檢查工具主要有PC-lint、Coverity、Fortify等,後面兩種都偏重量級,Coverity還需要提交結果到服務器,不便於隨身攜帶、隨時使用,PC-lint小巧方便,歷史悠久,使用廣泛,雖然被詬病爲誤報率高,但還是一個很有價值,值得隨身攜帶的工具。特別是合理的配置選項減少誤報之後,更是可以大幅提高查錯、排錯的效率。
    PC-Lint9出來兩年了,很早就聽說它相比上一個版本改進非常大,可惜一直沒發現可以下載試用的地方。今天居然發現了,於是迫不及待的下載試用了一回。
 

一、下載

csdn上可以找到下載地址:
 
最新的patch可以到Gimpel官方網站下載:
 

二、揭開PC-Lint9的神祕面紗

PC-lint9下載之後,主要的就是一個安裝文件。安裝過程也沒什麼特別,一路next安裝下來,就在指定目錄釋放了lint相關的文件。
本以爲進入lint安裝目錄會發現驚喜,結果出乎預料,跟8.0版本沒什麼區別,還是lint-nt.exe、和lnt目錄,甚至裏面的選項文件名也都沒什麼變化。我有點不敢相信自己的眼睛,命令行查詢一下版本:
D:\PC-Lint9>lint-nt -v
PC-lint for C/C++ (NT) Vers. 9.00a, Copyright Gimpel Software 1985-2008
哦,看來還真是9.0版本,這個9.0版本跟8.0還是一脈相承的啊。
 

三、升級到最新版本

從官方網站下載最新的所有patch文件,解壓縮到lint安裝目錄,然後寫一個批處理依次patch就升級到了最新版本。
目錄結構如下:
升級過程:
D:\PC-Lint9>update.bat

D:\PC-Lint9>PATCH.EXE LP9-A-B.RTP

D:\PC-Lint9>PATCH.EXE LP9-B-C.RTP

D:\PC-Lint9>PATCH.EXE LP9-C-D.RTP

D:\PC-Lint9>PATCH.EXE LP9-D-E.RTP

D:\PC-Lint9>PATCH.EXE LP9-E-F.RTP

D:\PC-Lint9>
D:\PC-Lint9>
D:\PC-Lint9>lint-nt -v
PC-lint for C/C++ (NT) Vers. 9.00f, Copyright Gimpel Software 1985-2010
整個升級過程與8.0也沒有差異。
 

四、在線試用

發現PC-lint有個在線測試:On-Line Demonstration of FlexeLint and PC-lint (aka FlexeLint for Windows) 
可以看到一些典型代碼的lint檢查結果,還可以修改代碼再測試,也可以上傳自己的代碼檢查。
 

五、若干例子

試用過在線之後也有在本地檢查一遍的衝動,於是拷貝一段簡單代碼在本地運行。(代碼參見:Simple Example (C++)
結果,PC-lint開始不斷抱怨了。
首先是這段代碼使用到string.h,找不到它在什麼位置。
很簡單,指定VC2008裏面的string.h路徑給它。
然後,PC-Lint又抱怨crtdefs.h中使用到的_WIN32、_MSC_VER沒定義。
好說,它抱怨什麼,我給什麼,定義了這些宏。突然,我覺得有點不對勁啊,兩次愚弄我了。
哦,我習慣於裸奔式的使用PC-lint了,從不借助官方根據不同編譯環境已經配置的選項文件,因爲之前我覺得只要知道lint原理,解決這些抱怨不成問題,而且所有選項在自己的控制中比較踏實,不用擔心官方的選項幹掉某個告警,從而漏掉了一個重要BUG。
該反省了,每次都費勁的配置選項以解決對我根本就沒價值的抱怨,這次我很乾脆的拿了co-msc90.lnt來用。果然好使,現在報告的告警全是我關心的了。
哦,原來PC-lint這樣用可以更簡單~~~~~~
 
附上我用的相關文件。
選項文件std.lnt:(第一個-i是指定co-msc90.lnt的路徑)
-i"D:\PC-Lint9.0\lnt"
co-msc90.lnt


-i"C:\Program Files\Microsoft Visual Studio 9.0\VC\include"
用於測試的代碼:
#include <string.h>

class X
    {
    int *p;
  public:
    X()
        { p = new int[20]; }
    void init()
        { memset( p, 20, 'a'  ); }
    ~X()
        { delete p; }
    };
檢查結果:(您可以仔細瞧瞧,起碼這些問題都不是誤報)
D:\Projects\Lab\PC-lint9-test\tests>lint-nt std.lnt simple.cpp
PC-lint for C/C++ (NT) Vers. 9.00f, Copyright Gimpel Software 1985-2010

--- Module:   simple.cpp (C++)
              _
        { p = new int[20]; }
simple.cpp(8) : Info 1732: new in constructor for class 'X' which has no
    assignment operator
simple.cpp(8) : Info 1733: new in constructor for class 'X' which has no copy
    constructor
                               _
        { memset( p, 20, 'a'  ); }
simple.cpp(10) : Warning 669: Possible data overrun for function 'memset(void
    *, int, unsigned int)', argument 3 (size=97) exceeds argument 1 (size=80)
    [Reference: file simple.cpp: lines 8, 10]
simple.cpp(8) : Info 831: Reference cited in prior message
simple.cpp(10) : Info 831: Reference cited in prior message
                  _
        { delete p; }
simple.cpp(12) : Warning 424: Inappropriate deallocation (delete) for 'new[]'
    data

    --- Wrap-up for Module: simple.cpp

Info 753: local class 'X' (line 3, file simple.cpp) not referenced
simple.cpp(3) : Info 830: Location cited in prior message

--- Global Wrap-up

Info 1714: Member function 'X::init(void)' (line 9, file simple.cpp) not
    referenced
simple.cpp(9) : Info 830: Location cited in prior message
 
就這個簡單例子來說,我們沒有看到9.0版本新鮮的東西,只是我們看到了它在使用方法上幾乎與8.0版本一樣。同時,溫故而知新,我們發現使用官方發佈的配套選項文件可以使得配置變得簡單。當然,這也許是地球人都知道的事實了:)
 
再看一個複雜點的例子:
檢查初始化順序問題。(代碼參見:Multi Module Initialization and Redundancies
D:\Projects\Lab\PC-lint9-test\tests\multimodule>lint-nt std.lnt files.lnt
PC-lint for C/C++ (NT) Vers. 9.00f, Copyright Gimpel Software 1985-2010

--- Module: a.cpp (C++)

--- Module: b.cpp (C++)
           _
  int b = a;
b.cpp(3) : Warning 1544: Value of variable 'a' (line 4, file a.cpp)
    indeterminate (order of initialization)
a.cpp(4) : Info 830: Location cited in prior message

--- Global Wrap-up
對於這個例子,需要注意,它檢查的是多個模塊,在線檢查有把多個模塊寫到一起的方式。實際使用中,是檢查多個文件,就是把多個文件羅列在files.lnt中。
只能通過一個.lnt文件列出多個需檢查的模塊,才能起到模塊檢查的作用。運行多次命令是不行的,比如運行 lint-nt a.cpp  和lint-nt b.cpp,它是不能檢查出模塊間的問題的,lint根本不知道模塊間是否有關聯。
本來我以爲,這個功能可能8.0版本沒有吧,結果發現8.0也有這個功能。呵呵。
 
再看一個多線程競爭條件檢查的例子:(代碼參見:Multi-threading (C)
D:\Projects\Lab\PC-lint9-test\tests>lint-nt multi-threading.cpp
PC-lint for C/C++ (NT) Vers. 8.00w, Copyright Gimpel Software 1985-2007

--- Module:   multi-threading.cpp (C++)
_
//lint -sem( reader, thread )
multi-threading.cpp  6  Warning 425: 'unrecognized name' in processing semantic
    'thread' at token 'thread'
_
//lint -sem( Lock::Lock, thread_lock )
multi-threading.cpp  7  Warning 425: 'unrecognized name' in processing semantic
    'thread_lock' at token 'thread_lock'
_
//lint -sem( Lock::~Lock, thread_unlock )
multi-threading.cpp  8  Warning 425: 'unrecognized name' in processing semantic
    'thread_unlock' at token 'thread_unlock'

D:\Projects\Lab\PC-lint9-test\tests>
D:\Projects\Lab\PC-lint9-test\tests>
D:\Projects\Lab\PC-lint9-test\tests>set path=D:\Tools\CMD\Lint\SmartLint\PC-Lint9.0

D:\Projects\Lab\PC-lint9-test\tests>lint-nt multi-threading.cpp
PC-lint for C/C++ (NT) Vers. 9.00f, Copyright Gimpel Software 1985-2010

--- Module:   multi-threading.cpp (C++)

--- Thread messages:

Warning 457: Function 'h(void)' of thread 'main(void)' has an unprotected write
    access to variable 'x' which is used by function 'h(void)' of thread
    'reader(void)'
Warning 458: Function 'h(void)' of thread 'main(void)' has an unprotected read
    access to variable 'y' which is modified by function 'g(void)' of thread
    'reader(void)'
 
這回9.0版本沒有令我們失望,根據上面8.0和9.0版本運行的比較可以看出,9.0版本支持-sem新的參數“thread、thread_lock、thread_unlock”,從而能夠檢查出未做多線程保護的一些問題。
當然,要檢查這樣的問題,是需要用-sem做些配置的。
 
其它例子:
1、強類型檢查。(代碼參見: Strong typedef Checking Example (C) 和 Dimensional Analysis (C++) 
typedef定義的類型,也進行檢查。(C/C++本身不會進行檢查的)
這在某些情況下還是非常有用。具體可參見代碼。
 
2、互斥鎖的誤用檢查。(代碼參見:Mutual Exclusion (C)
包括:
 (1)沒有釋放。
 (2)釋放沒有與之匹配的加鎖。
 (3)多個分支有不同的加鎖狀態。
 

六、輔助工具

有很多PC-lint輔助工具,比如:
Cleanscape C++lint    更好的集成到Visual Studio。(收費軟件)
Visual Lint from Riverblade  可以在寫代碼過程中默默的在背後執行lint,這可能對我們比較有幫助。(收費軟件,這裏可以下載試用http://bbs.pediy.com/showthread.php?p=660418,但經嘗試Win7+VC2008不能啓動,覆蓋破解dll之後啓動報錯,不能顯示Visual Lint插件)
 
更多輔助工具請參見:
 

七、PC-Lint9.0新增功能

最後,羅列一下PC-Lint9.0的一些新增功能。
1、線程分析,可以檢查鎖使用的正確性和可能缺少鎖保護的變量。
2、通過預編譯頭大幅提升複雜項目的檢查速度。
3、棧空間使用統計,可以彙總出單個應用的最大棧空間需求,只要程序中不存在遞歸併且是具有流程確定性的。
4、支持Deprecate聲明。
5、可以針對一個(組)特定的符號開啓某個檢查項。易用性的很大改善,使得我們可以更好的運用一些提示級別的檢查項。
6、“宏淨化(Macro Scavenging)”。這個功能想必是爲了解決PC-Lint與GCC不同版本配合中需要大量人工配置的尷尬局面吧。聽起來不錯的一個解決方案,具有較強的通用性。
7、新加入的“-sem”語法:成員方法的初始化/回收職能標識、inout類參數標識、多線程分析輔助標識…… 這一系列補充標識都是非常有實用價值的,尤其是前兩項,解決了C++工程維護中擴充成員時經常碰到的忘記寫配套的初始化或釋放處理的頑疾。
 

八、參考資料

2、PC-Lint終於迎來了9.0版本(http://blog.oasisfeng.com/2008/10/29/pclint-hits-v9/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章