pc_lint的⽤法转
PC-Lint的检查分很多种类,有强类型检查、变量值跟踪、语义信息、赋值顺序检查、弱定义检查、格式检查、缩进检查、const变量检查和volatile变量检查等等。对每⼀种检查类型,PC-Lint都有很多详细的选项,⽤以控制PC-Lint的检查效果。PC-Lint的选项有300 多种,这些选项可以放在注释中(以注释的形式插⼊代码中),例如:
/*lint option1 option2 ... optional commentary */ 选项可以有多⾏
//lint option1 option2 ... optional commentary 选项仅为⼀⾏(适⽤于C++)
选项间要以空格分开,lint命令⼀定要⼩写,并且紧跟在/*或//后⾯,不能有空格。如果选项由类似于操作符和操作数的部分组成,例如 -esym(534, printf, scanf, operator new),其中最后⼀个选项是operator new,那么在operator和new中间只能有⼀个空格。PC-Lint的选项还可以放在宏定义中,当宏被展开时选项才⽣效。例如:
#define DIVZERO(x) /*lint -save -e54 */ ((x) /0) /*lint -restore */ 允许除数为0⽽不告警
下⾯将分别介绍PC-Lint常⽤的,也是⽐较重要的代码检查类型,并举例介绍了各个检查类型下可能出现的告警信息以及常⽤选项的⽤法:
3.1 强类型检查
强类型检查选项“-strong”和它的辅助(补充)选项“-index”可以对typedef定义的数据类型进⾏强类型检查,以保证只有相同类型之间的变量才能互相赋值,强类型检查选项strong的⽤法是:
-strong( flags[, name] ... )
strong选项必须在typedef定义类型之前打开,否则PC-Lint就不能识别typedef定义的数据类型,类型检查就会失效。flags参数可以是A、J、X、B、b、l和f,相应的解释和弱化字符在表 2 中列出:
表 2 强类型检查strong选项和参数表 A 对强类型变量赋值时进⾏类型检查,这些赋值语句包括:直接赋值、返回值、参数传递、初始化。
A参数后⾯可以跟以下字符,⽤来弱化A的检查强度:
i 忽略初始化
r 忽略Return语句
p 忽略参数传递
a 忽略赋值操作
c 忽略将常量赋值(包括整数常量、常量字符串等)给强类型的情况
z 忽略Zero赋值,Zero定义为任何⾮强制转换为强类型的0常量。例如:0L和(int)0都是Zero,
但是(HANDLE)0当HANDLE是⼀个强类型的时候就不是Zero。(HANDLE *)0也不是例如使⽤-strong(Ai,BITS)设置,PC-Lint将会对从⾮BITS类型数据向BITS类型数据赋值的代码发出告警,但是忽略变量初始化时的此类赋值。
X 当把强类型的变量赋指给其他变量的时候进⾏类型检查。弱化参数i, r, p, a, c, z同样适⽤于X并起相同的作⽤。
J 选项是当强类型与其它类型进⾏如下的⼆进制操作时进⾏检查,下⾯是J的参数:
e 忽略==、!=和?:操作符
r 忽略>、>=、<;和<=
o 忽略+、-、*、/、%、|、&和^
c 忽略该强类型与常量进⾏以上操作时的检查
z 忽略该强类型与Zero进⾏以上操作时的检查
使⽤忽略意味着不会产⽣告警信息。举个例⼦,如果Meters是个强类型,那么它只在判断相等和其他关系操作时才会被正确地检查,其它情况则不检查,在这个例⼦中使⽤J选项是正确的。
B B选项有两个效果:
1. 出于强类型检查的⽬的,假设所有的Boolean操作返回⼀个和Type兼容的类型,所谓Boolean操作就是那些指⽰结果为true或false的操作,包括前⾯提到的四种关系运算符和两种等于判断符,取反操作符!,⼆元操作符&&和||。
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长度的位域被缺省假定为Boolean类型。
这些选项字符的顺序对功能没有影响。但是A和J选项的弱化字符必须紧跟在它们之后。B选项和b选项不能同时使⽤,f选项必须搭配B选项或b选项使⽤,如果不指定这些选项,-strong的作⽤就是仅仅声明type为强类型⽽不作任何检查。下⾯⽤⼀段代码演⽰-strong选项的⽤法:switch语句具体例子
//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语句中的⽐较操作就会被认为与函数类型不匹配。第⼆个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选项抑制了这个告警。
强类型也可⽤于位域,出于强类型检查的⽬的,先假定位域中最长的⼀个字段是优势Boolean类型,如果没有优势Boolean或位域中没有哪个字段⽐其它字段长,这个类型从位域被切开的位置开始成为“散”类型,例如:
//lint -strong( AJXb, Bool )
//lint -strong( AJX, BitField )
typedef int Bool;
typedef unsigned BitField;
struct foo
{
unsigned a:1, b:2;
BitField c:1, d:2, e:3;
} x;
void f()
{
x.a = (Bool) 1; // OK
x.b = (Bool) 0; // strong type violation
x.a = 0; // strong type violation
x.b = 2; // OK
x.c = x.a; // OK
118
x.e = 1; // strong type violation
x.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] ... )
这个选项是对strong选项的补充,它可以和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 Count can index a Temperature
typedef float Temperature;
typedef int Count;
Temperature t[100]; // OK because of d flag
Temperature *pt = t; // pointers are also checked
// ... within a function
Count i;
t[0] = t[1]; // Warnings, no c flag
for( i = 0; i < 100; i++ )
t[i] = 0.0; // OK, i is a Count
119
pt[1] = 2.0; // Warning
i = pt - t; // OK, pt-t is a Count
上⾯的例⼦中,Temperature是被强索引类型,Count是强索引类型。如果没有使⽤d选项,数组的长度将被映射成固有的类型:Temperature t[ (Count) 100 ];
但是,这是个⼩⿇烦,像下⾯那样将数组长度定义成常量更好⼀些:
#define MAX_T (Count) 100
Temperature t[MAX_T];
这样做还有⼀个好处就是同样的MAX_T还可以⽤在for语句中,⽤于限制for语句的范围。需要注意的是,指向强被索引类型的指针(例如上⾯的pt)如果⽤在[]符号(数组符号)中也会被检查类型。其实,⽆论何时,只要将⼀个值加到⼀个指向强被索引类型的指针时,这个值就会被检查以确认它是⼀个强索引类型。此外,强被索引指针如果减去⼀个值,其结果被认为是平常的强索引,所以下⾯的例⼦就不会产⽣告警:
i = pt - t;
3.2 变量值跟踪
3.2.1 变量值初始化跟踪
早期的变量值跟踪技术主要是对变量值的初始化进⾏跟踪,和变量初始化相关的LINT消息主要是644, 645 ("变量可能没有初始化"), 771, 772 ("不可靠的初始化"), 530 ("未初始化的"), and 1401 - 1403 ("成员 ... 未初始化")。以下⾯的代码为例:
if( a ) b = 6;
else c = b; // 530 message
a = c; // 645 message
假设b和c在之前都没有初始化,PC-Lint就会报告b没有初始化(在给c赋值的时候)和c可能没有被初始化(在给a赋值的时候)的消息。⽽while和for循环语句和上⾯的if语句稍微有所不同,⽐较下⾯的代码:
while ( n-- )
{
b = 6;
...
}
c = b; //772 message
假设b在使⽤之前没有被初始化,这⾥会报告b可能没有初始化的消息(当给c赋值时)。之所以会有这样的区别,是因为程序设计者可能知道这样的循环体总是会被⾄少执⾏⼀次。相反,前⾯的if语句,对于程序设计者来说⽐较难以确定if语句是否总会被执⾏,因为如果是这样的话,这样的if语句就是多余的,应该被去掉。While语句和if⽐较相似,看下⾯的例⼦:
switch ( k )
{
case 1: b = 2; break;
case 2: b = 3;
/* Fall Through */
case 3: a = 4; break;
default: error();
}
c = b; //645 message
尽管b在两个不同的地⽅被赋值,但是仍然存在b没有被初始化的可能。因此,当b赋值给c的时候,就会产⽣可能没有初始化的消息。为了解决这个问题,你可以在switch语句之前给b赋⼀个默认值。这样PC-Lint就不会产⽣告警消息,但是我们也失去了让PC-Lint检查后续的代码修改引起的变量初始化问题的机会。更好的⽅法是修改没有给b赋值的case语句。
如果error()语句代表那些“不可能发⽣”的事情发⽣了,那么我们可以让PC-Lint知道这⼀段其实是不可能执⾏的,下⾯的代码表明了这⼀点:
switch ( k )
{
case 1: b = 2; break;
case 2:
case 3: b = 3; a = 4; break;
default: error();
/*lint -unreachable */
}
c = b;
注意:这⾥的-unreachable应该放在error()后⾯,break的前⾯。另外⼀个产⽣”没有初始化”告警的⽅式是传递⼀个指针给free(或者采⽤相似的⽅法)。⽐如:
if( n ) free( p );
...
p->value = 3;
在访问p的时候会产⽣p可能没有被初始化的消息。对于goto语句,前向的goto可能产⽣没有初始化消息,⽽向后的goto 会被忽略掉这种检查。
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 变量值跟踪
变量值跟踪技术从赋值语句、初始化和条件语句中收集信息,⽽函数的参数被默认为在正确的范围内,只有在从函数中可以收集到的信息
与此不符的情况下才产⽣告警。与变量值跟踪相关的消息有:
(1) 访问地址越界消息(消息415,661,796)
(2) 被0除消息(54,414,795)
(3) NULL指针的错误使⽤(413,613,794)
(4) ⾮法指针的创建错误(416,662,797)
(5) 冗余的布尔值测试(774)
看下⾯的例⼦:
int a[10];
int f()
{
int k;
k = 10;
return a[k]; // Warning 415
}
这个语句会产⽣警告415(通过 '[' 访问越界的指针),因为PC-Lint保存了赋给k的值,然后在使⽤k的时候进⾏了判断。如果我们把上⾯的例⼦稍加修改:
int a[10];
int f( int n )
{
int k;
if ( n ) k = 10;
else k = 0;
return a[k]; // Warning 661
}
这样就会产⽣告警 661 (可能访问越界指针)。使⽤“可能”是因为不是所有的路径都会把10赋值给k。PC-Lint不仅收集赋值语句和初始化,还从条件语句中收集值的信息。⽐如下⾯的例⼦:
int a[10];
int f( int k, int n )
{
if ( k >= 10 ) a[0] = n;
return a[k]; // Warning 661 -- k could be 10
}
这⾥仍然产⽣661告警,因为PC-Lint检测到,在使⽤k的时候,k的值>=10。另外,对于函数来说,它总是假设K是正确的,程序使⽤者知道他们要做些什么,所以下⾯的语句不会产⽣告警:
int a[10];
int f( int k, int n )
{ return a[k+n]; } // no warning
和检查变量没有初始化⼀样,还可以检查变量的值是否正确。⽐如,如果下⾯例⼦中的循环⼀次都没有运⾏,k可能会超出范围。这时候会产⽣消息796 (可预见的地址访问越界).
int a[10];
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 a[k]; // Info 796 - - k could still be 10
}
下⾯的例⼦演⽰了可能使⽤NULL指针的问题:
int *f( int *p )
{
if ( p ) printf( "\n" ); // So -- p could be NULL
printf( "%d", *p ); // Warning
return p + 2; // Warning
}
这⾥会产⽣两个告警,因为可能使⽤了NULL指针,很明显,这两个语句应该在if语句的范围内。为了使你的程序更加健壮,你可能需要打开Pointer- parameter-may-be-NULL这个开关(+fpn)。这个选项假设所有传递到函数中的指针都有可能是NULL的。数组边界值在⾼位被检测,也就是说
int a[10]; ... a[10] = 0;
被检测了,⽽a[-1]却检测不到。PC-Lint中有两个消息是和指针的越界检查有关的,⼀个是越界指针的创建,另外⼀个是越界指针的访问,也就是通过越界指针获取值。在ANSI C([1]3.3.6)中,允许创建指向超过数组末尾⼀个单元的指针,⽐如:
int a[10];
f( a + 10 ); // OK
f( a + 11 ); // error
但是上⾯创建的两个指针,都是不能访问的,⽐如:
int a[10], *p, *q;
p = a + 10; // OK
*p = 0; // Warning (access error)
p[-1] = 0; // No Warning
q = p + 1; // Warning (creation error)
q[0] = 0; // Warning (access error)
布尔条件检查不象指针检查那么严格,但是它会对恒真的布尔条件产⽣告警,⽐如:
if ( n > 0 ) n = 0;
else if ( n <= 0 ) n = -1; // Info 774
上⾯的代码会产⽣告警(774),因为第⼆个条件检查是恒真的,可以忽略。这种冗余代码不会导致问题,但它的产⽣通常是因为逻辑错误或⼀种错误可能发⽣的征兆,需要详细的检查。
3.2.3 使⽤assert(断⾔)进⾏补救
在某些情况下,虽然根据代码我们可以知道确切的值,但是PC-Lint却⽆法获取所有情况下变量的值的范围,这时候会产⽣⼀些错误的告警信息,我们可以使⽤assert语句增加变量取值范围信息的⽅法,来抑
制这些错误的告警信息的产⽣。下⾯举例来说明:
char buf[4];
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 reference
PC-Lint⽆法知道在所有情况下变量的值是多少。在上⾯的例⼦中,产⽣告警的语句其实并不会带来什么危害。我们可以直接使⽤
*p = 'a'; //lint !e661
来抑制告警。另外,我们还可以使⽤assert⼯具来修正这个问题:
#include <assert.h>
...
char buf[4];
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⼀起产⽣上⾯的效果,你需要在编译选项⽂件中添加⼀个选项。例如,假设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), _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( int n )
5 { return 10 / n; }
在这个例⼦中,f()被调⽤的时候使⽤0作为参数,这将导致原本没有问题的10/n语句产⽣被0除错误,使⽤命令lin -u t1.cpp可以得到以下输出:
--- Module: t1.cpp
During 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”,紧接着是函数调⽤发⽣的位置,函数名称以及参数,再下来就是错误信息。如果错误信息中缺少了错误再现时的错误⾏和⽤来标记错误位置的指⽰信息,这是因为检查到错误的时候代码(被调⽤函数的代码)已经⾛过了。如果像下⾯⼀样调换⼀下两个函数的位置:
t2.cpp:
1 int f( int n )
2 { return 10 / n; }
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论