博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

揭开PC-Lint9的神秘面纱

Posted on 2010-11-03 00:44  sinojelly  阅读(6523)  评论(4编辑  收藏  举报

前言

    今天,又定位了一个令人懊恼的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/