[计算机软件及应用]静态代码检查工具PC-Lint的使用方法.doc

上传人:音乐台 文档编号:1992134 上传时间:2019-01-29 格式:DOC 页数:39 大小:883.45KB
返回 下载 相关 举报
[计算机软件及应用]静态代码检查工具PC-Lint的使用方法.doc_第1页
第1页 / 共39页
[计算机软件及应用]静态代码检查工具PC-Lint的使用方法.doc_第2页
第2页 / 共39页
[计算机软件及应用]静态代码检查工具PC-Lint的使用方法.doc_第3页
第3页 / 共39页
亲,该文档总共39页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《[计算机软件及应用]静态代码检查工具PC-Lint的使用方法.doc》由会员分享,可在线阅读,更多相关《[计算机软件及应用]静态代码检查工具PC-Lint的使用方法.doc(39页珍藏版)》请在三一文库上搜索。

1、静态代码检查工具PC-Lint(一)摘要:C/C+语言的语法拥有其它语言所没有的灵活性,这种灵活性带来了代码效率的提升,但相应增加了代码中存在隐患的可能性。静态代码检查工具PC-Lint则偏重于代码的逻辑分析,它能够发现代码中潜在的错误,比如数组访问越界、内存泄漏、使用未初始化变量等。本文将介绍如何安装和配置PC-Lint代码检查工具以及如何将PC-Lint与常见的代码编辑软件集成。关键词:代码检查 PC-Lint 规则 选项目 录 摘 要1 引 言2 PC-Lint介绍3 PC-Lint的代码检查功能 3.1 强类型检查 3.2 变量值跟踪 3.3 赋值顺序检查 3.4 弱定义检查 3.5

2、格式检查 3.6 缩进检查 3.7 const变量检查 3.8 volatile变量检查4 PC-Lint软件使用方法 4.1 安装与配置 4.2 PC-Lint与常用开发工具的集成(Visual C+,Source Insight,UEdit)5 总结参考文献附录一 PC-Lint 重要文件说明附录二 错误信息禁止选项说明附录三 PC-Lint检测中的常见错误一 引言 C/C+语言的语法拥有其它语言所没有的灵活性,这种灵活性带来了代码效率的提升,但相应也使得代码编写具有很大的随意性,另外C/C+编译器不进行强制类型检查,也不做任何边界检查,这就增加了代码中存在隐患的可能性。如果能够在代码提交

3、测试之前发现这些潜在的错误,就能够极大地减轻测试人员的压力,减少软件项目的除错成本,可是传统的C/C+编译器对此已经无能为力,这个任务只能由专用的代码检查工具完成。目前有很多C/C+静态代码检查工具,其中Logiscope RuleChecker和PC-Lint是目前应用比较广泛的两个工具。这两个检查工具各有特色,Logiscope RuleChecker倾向于代码编码规范的检查,比如代码缩进格式、case语句书写规范、函数声明和布尔表达式的编写规则等,而PC-Lint则偏重于代码的逻辑分析,它能够发现代码中潜在的错误,比如数组访问越界、内存泄漏、使用未初始化变量等。本文将介绍如何安装和配置P

4、C-Lint代码检查工具以及将PC-Lint与常见的代码编辑软件,如Visual C+,Source Insight集成的方法,同时还将简要介绍一些PC-Lint常用的代码检查选项。二 PC-Lint介绍 PC-Lint是GIMPEL SOFTWARE公司开发的C/C+软件代码静态分析工具,它的全称是PC-Lint/FlexeLint for C/C+,PC-Lint能够在Windows、MS-DOS和OS/2平台上使用,以二进制可执行文件的形式发布,而FlexeLint 运行于其它平台,以源代码的形式发布。PC-lint在全球拥有广泛的客户群,许多大型的软件开发组织都把PC-Lint检查作为

5、代码走查的第一道工序。 PC-Lint不仅能够对程序进行全局分析,识别没有被适当检验的数组下标,报告未被初始化的变量,警告使用空指针以及冗余的代码,还能够有效地帮你提出许多程序在空间利用、运行效率上的改进点。 通过下面的例子就可以看出PC-Lint工具的强大功能: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 name11 = Joe Jakeson; 9: 10: nm = n * m; 11: temp = p =

6、 ? null : p; 12: for( i = 0; i 0 ) result = 1; 20: else if( kk 、=、和=o 忽略+、-、*、/、%、|、&和c 忽略该强类型与常量进行以上操作时的检查z 忽略该强类型与Zero进行以上操作时的检查使用忽略意味着不会产生告警信息。举个例子,如果Meters是个强类型,那么它只在判断相等和其他关系操作时才会被正确地检查,其它情况则不检查,在这个例子中使用J选项是正确的。BB选项有两个效果:1. 出于强类型检查的目的,假设所有的Boolean操作返回一个和Type兼容的类型,所谓Boolean操作就是那些指示结果为true或false的

7、操作,包括前面提到的四种关系运算符和两种等于判断符,取反操作符!,二元操作符&和|。2. 在所有需要判断Bolean值的地方,如if语句和while语句,都要检查结果是否符合这个强类型,否则告警。例如if(a).当a为int时,将产生告警,因为int与Bolean类不兼容,所以必须改为if(a != 0)。b仅仅假定每一个Bolean类操作符都将返回一个与Type类型兼容的返回值。与B选项相比,b选项的限制比较宽松。l库标志,当强类型的值作为参数传递给库函数等情况下,不产生告警。f与B或b连用,表示抑止对1bit长度的位域是Boolean类型的假定,如果不选该项表示1bit长度的位域被缺省假定

8、为Boolean类型。 这些选项字符的顺序对功能没有影响。但是A和J选项的弱化字符必须紧跟在它们之后。B选项和b选项不能同时使用,f选项必须搭配B选项或b选项使用,如果不指定这些选项,-strong的作用就是仅仅声明type为强类型而不作任何检查。下面用一段代码演示-strong选项的用法:/lint -strong(Ab,Bool) typedef int Bool;Bool gt(int a, b) if(a) return a b; / OK else return 0; / Warning例子代码中Bool被声明成强类型,如果没有指定b选项,第一个return语句中的比较操作就会被认为

9、与函数类型不匹配。第二个return语句导致告警是因为0不是各Bool类型,如果添加c选项,例如-strong(Acb,Bool),这个告警就会被抑制。再看一个例子:/*lint -strong( AJXl, STRING ) */typedef char *STRING;STRING s;.s = malloc(20);strcpy( s, abc );由于malloc和strcpy是库函数,将malloc的返回值赋给强类型变量s或将强类型变量s传递给strcpy时会产生强类型冲突,不过l选项抑制了这个告警。 强类型也可用于位域,出于强类型检查的目的,先假定位域中最长的一个字段是优势Bool

10、ean类型,如果没有优势Boolean或位域中没有哪个字段比其它字段长,这个类型从位域被切开的位置开始成为“散”类型,例如:/lint -strong( AJXb, Bool )/lint -strong( AJX, BitField )typedef int Bool;typedef unsigned BitField;struct foounsigned a:1, b:2;BitField c:1, d:2, e:3; x;void f()x.a = (Bool) 1; / OKx.b = (Bool) 0; / strong type violationx.a = 0; / strong

11、 type violationx.b = 2; / OKx.c = x.a; / OK118x.e = 1; / strong type violationx.e = x.d; / OK上面例子中,成员a和c是强类型Bool,成员d和e是BitField类型,b不是强类型。为了避免将只有一位的位域假设成Boolean类型,需要在声明Boolean的-strong中使用f选项,上面的例子就应该改成这样:-strong(AJXbf,Bool)。 另一个强类型检查选项是index,index的用法是:-index( flags, ixtype, sitype , sitype . )这个选项是对st

12、rong选项的补充,它可以和strong选项一起使用。这个选项指定ixtype是一个排除索引类型,它可以和Strongly Indexed类型sitype的数组(或指针)一起使用,ixtype和sitype被假设是使用typedef声明的类型名称。flags可以是c或 d,c允许将ixtype和常量作为索引使用,而d允许在不使用ixtype的情况下指定数组的长度(Dimensions)。下面是一个使用index 的例子:/lint -strong( AzJX, Count, Temperature )/lint -index( d, Count, Temperature )/ Only Cou

13、nt can index a Temperaturetypedef float Temperature;typedef int Count;Temperature t100; / OK because of d flagTemperature *pt = t; / pointers are also checked/ . within a functionCount i;t0 = t1; / Warnings, no c flagfor( i = 0; i value = 3;在访问p的时候会产生p可能没有被初始化的消息。对于goto语句,前向的goto可能产生没有初始化消息,而向后的goto

14、 会被忽略掉这种检查。if ( a ) goto label;b = 0;label: c = b;当在一个大的项目中使用未初始化变量检查时,可能会产生一些错误的报告。这种报告的产生,很大一部分来自于不好的程序设计风格,或者包括下面的结构:if( x ) initialize y.if( x ) use y当出现这种情况时,可以采用给y赋初始值的方式,或者利用选项-esym(644,y)关掉变量y上面的初始化检查。3.2.2 变量值跟踪 变量值跟踪技术从赋值语句、初始化和条件语句中收集信息,而函数的参数被默认为在正确的范围内,只有在从函数中可以收集到的信息与此不符的情况下才产生告警。与变量值跟

15、踪相关的消息有:(1) 访问地址越界消息(消息415,661,796)(2) 被0除消息(54,414,795)(3) NULL指针的错误使用(413,613,794)(4) 非法指针的创建错误(416,662,797)(5) 冗余的布尔值测试(774) 看下面的例子:int a10;int f()int k;k = 10;return ak; / Warning 415这个语句会产生警告415(通过 访问越界的指针),因为PC-Lint保存了赋给k的值,然后在使用k的时候进行了判断。如果我们把上面的例子稍加修改:int a10;int f( int n )int k;if ( n ) k =

16、 10;else k = 0;return ak; / Warning 661这样就会产生告警 661 (可能访问越界指针)。 使用“可能”是因为不是所有的路径都会把10赋值给k。PC-Lint不仅收集赋值语句和初始化,还从条件语句中收集值的信息。比如下面的例子:int a10;int f( int k, int n )if ( k = 10 ) a0 = n;return ak; / Warning 661 - k could be 10这里仍然产生661告警,因为PC-Lint检测到,在使用k的时候,k的值=10。另外,对于函数来说,它总是假设K是正确的,程序使用者知道他们要做些什么,所以

17、下面的语句不会产生告警:int a10;int f( int k, int n ) return ak+n; / no warning和检查变量没有初始化一样,还可以检查变量的值是否正确。比如,如果下面例子中的循环一次都没有运行,k可能会超出范围。这时候会产生消息796 (可预见的地址访问越界).int a10;int f(int n, int k)int m = 2;if( k = 10 ) m+; / Hmm - So k could be 10, eh?while( n- ) m+; k = 0; return ak; / Info 796 - - k could still be 10

18、下面的例子演示了可能使用NULL指针的问题:int *f( int *p )if ( p ) printf( n ); / So - p could be NULLprintf( %d, *p ); / Warningreturn p + 2; / Warning这里会产生两个告警,因为可能使用了NULL指针,很明显,这两个语句应该在if语句的范围内。为了使你的程序更加健壮,你可能需要打开Pointer- parameter-may-be-NULL这个开关(+fpn)。这个选项假设所有传递到函数中的指针都有可能是NULL的。数组边界值在高位被检测,也就是说int a10; . a10 = 0;

19、被检测了,而a-1却检测不到。PC-Lint中有两个消息是和指针的越界检查有关的,一个是越界指针的创建,另外一个是越界指针的访问,也就是通过越界指针获取值。在ANSI C(13.3.6)中,允许创建指向超过数组末尾一个单元的指针,比如:int a10;f( a + 10 ); / OKf( a + 11 ); / error但是上面创建的两个指针,都是不能访问的,比如:int a10, *p, *q;p = a + 10; / OK*p = 0; / Warning (access error)p-1 = 0; / No Warningq = p + 1; / Warning (creatio

20、n error)q0 = 0; / Warning (access error)布尔条件检查不象指针检查那么严格,但是它会对恒真的布尔条件产生告警,比如:if ( n 0 ) n = 0;else if ( n = 0 ) n = -1; / Info 774上面的代码会产生告警(774),因为第二个条件检查是恒真的,可以忽略。这种冗余代码不会导致问题,但它的产生通常是因为逻辑错误或一种错误可能发生的征兆,需要详细的检查。3.2.3 使用assert(断言)进行补救 在某些情况下,虽然根据代码我们可以知道确切的值,但是PC-Lint却无法获取所有情况下变量的值的范围,这时候会产生一些错误的告警

21、信息,我们可以使用assert语句增加变量取值范围信息的方法,来抑制这些错误的告警信息的产生。下面举例来说明:char buf4;char *p;strcpy( buf, a );p = buf + strlen( buf ); / p is possibly (buf+3)p+; / p is possibly (buf+4)*p = a; / Warning 661 - possible out-of-bounds referencePC-Lint无法知道在所有情况下变量的值是多少。在上面的例子中,产生告警的语句其实并不会带来什么危害。我们可以直接使用*p = a; /lint !e661

22、来抑制告警。另外,我们还可以使用assert工具来修正这个问题:#include .char buf4;char *p;strcpy( buf, a );p = buf + strlen( buf );assert( p buf + 3 ); / p is possibly (buf+2)p+; / p is possibly (buf+3)*p = a; / no problem由于assert在NDEBUG被定义时是一个空操作,所以要保证Lint进行的时候这个宏没有被定义。 为了使assert()和你的编译器自带的assert.h一起产生上面的效果,你需要在编译选项文件中添加一个选项。例如

23、,假设assert 是通过以下的编译器宏定义实现的:#define assert(p) (p) ? (void)0 : _A(.)考虑到_A()会弹出一个消息并且不会返回,所以这个需要添加的选项就是:-function( exit, _A )这个选项将exit函数的一些非返回特征传递给_A函数。做为选择结果,编译器可能将assert实现成一个函数,例如:#define assert(k) _Assert(k,.)为了让PC-lint知道_Assert是一个assert函数,你需要使用-function( _assert, _Assert )选项或-function( _assert(1), _

24、Assert(1) )选项复制_assert()函数的语义许多编译器的编译选项文件中已经存在这些选项了,如果没有的话,你可以复制一个assert.h文件到PC-lint目录下(这个目录由于使用了-i选项,文件搜索的顺序优先于编译器的头文件目录)。3.2.4 函数内变量跟踪 PC-Lint的函数值跟踪功能会跟踪那些将要传递给函数(作为函数参数)变量值,当发生函数调用时,这些值被用来初始化函数参数。这种跟踪功能被用来测定返回值,记录额外的函数调用,当然还可以用来侦测错误。考察下面的例子代码:t1.cpp:1 int f(int);2 int g()3 return f(0); 4 int f( i

25、nt n )5 return 10 / n; 在这个例子中,f()被调用的时候使用0作为参数,这将导致原本没有问题的10/n语句产生被0除错误,使用命令lin -u t1.cpp可以得到以下输出:- Module: t1.cppDuring Specific Walk:File t1.cpp line 3: f(0)t1.cpp 5 Warning 414: Possible division by 0 Reference:File t1.cpp: line 3你第一个注意到的事情是短语“During Specific Walk”,紧接着是函数调用发生的位置,函数名称以及参数,再下来就是错误信

26、息。如果错误信息中缺少了错误再现时的错误行和用来标记错误位置的指示信息,这是因为检查到错误的时候代码(被调用函数的代码)已经走过了。如果像下面一样调换一下两个函数的位置:t2.cpp:1 int f( int n )2 return 10 / n; 3 int g()4 return f(0); 这种情况下就不会出现被0除的告警,因为此时f(0)在第四行,函数f()的代码已经过了,在这种情况下就需要引入multi-pass选项。如果在刚才的例子中使用lin -u -passes(2) t2.cpp命令,那么输出就变成:- Module: t2.cpp/ Start of Pass 2 /- M

27、odule: t2.cppDuring Specific Walk:File t2.cpp line 4: f(0)t2.cpp 2 Warning 414: Possible division by 0 Reference:File t2.cpp: line 4使用-passes(2)选项将会检查代码两遍,一些操作系统不支持在命令行中使用-passes(2),对于这样的系统,可以使用-passes=2 或 -passes2代替。通过冗长的信息可以看出来,以pass 2开始表示第一次检查没有产生告警信息。这一次得到的错误信息和前一次不同,在某种情况下我们可以推断出指定函数调用的返回值,至少可以

28、得到一些返回值的属性。以下面的模块为例:t3.cpp:1 int f( int n )2 return n - 1; 3 int g( int n )4 return n / f(1); 使用命令 lin -u -passes(2) t3.cpp,可以得到以下输出信息:- Module: t3.cpp/ Start of Pass 2 /- Module: t3.cpp return n / f(1); t3.cpp 4 Warning 414: Possible division by 0 Reference:File t3.cpp: lines 2, 4第一遍检查我们知道调用函数f()传递

29、的参数是1,第二遍检查先处理了函数f(),我们推断出这个参数将导致返回结果是0,当第二遍检查开始处理函数g() 的时候,产生了被0除错误。应该注意到这个信息并不是在短语“During Specific Walk”之前出现的,这是因为错误是在对函数g()进行正常的处理过程中检测到的,此时并没有使用为函数g()的参数指定的值。指定的函数调用能够产生附加的函数调用,如果我们pass足够多的检测次数,这个过程可能会重复发生,参考下面的代码:t4.cpp:1 int f(int);2 int g( int n )3 return f(2); 4 int f( int n )5 return n / f(

30、n - 1); 第五行的分母f(n-1)并不会引起怀疑,直到我们意识到f(2)调用将导致f(1)调用,最终会调用f(0),迫使最终的返回值是0。使用下面的命令行:lin -u -passes(3) t4.cpp,输出结果如下:- Module: t4.cpp return f(2); t4.cpp 3 Info 715: Symbol n (line 2) not referenced/ Start of Pass 2 /- Module: t4.cpp/ Start of Pass 3 /- Module: t4.cppDuring Specific Walk:File t4.cpp line 3: f(2)File t4.cpp line 5: f(1)t4.cpp 5 Warning 414: Possible divisi

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1