PC-LINT
介绍
PC-Lint for C/C++是由Gimpel软件公司于1985年开发的代码静态分析工具,它能有效地发现程序语法错误、潜在的错误隐患、不合理的编程习惯等。
C语言的灵活性带来了代码效率的提升,但相应带来了代码编写的随意性,另外C编译器不进行强制类型检查,也带来了代码编写的隐患。PC-Lint能识别并报告C语言中的编程陷阱和格式缺陷的发生。它进行程序的全局分析,能识别没有被适当检验的数组下标,报告未被初始化的变量,警告使用空指针,冗余的代码,等等。软件除错是软件项目开发成本和延误的主要因素。PC-lint能够帮你在程序动态测试之前发现编码错误,这样消除错误的成本更低。使用PC-Lint在代码走读和单元测试之前进行检查,可以提前发现程序隐藏错误,提高代码质量,节省测试时间。并提供编码规则检查,规范软件人员的编码行为。
告警信息
PC-Lint能够检查出很多语法错误和语法上正确的逻辑错误,PC-Lint为大部分错误消息都分配了一个错误号,编号小于1000的错误号是分配给C语言的,编号大于1000的错误号则用来说明C++的错误消息。如下列出了PC-Lint告警消息的详细分类:
错误说明 |
C |
C++ |
告警级别 |
语法错误 |
1-199 |
1001-1199 |
1 |
内部错误 |
200-299 |
|
0 |
致命 |
300-399 |
|
0 |
告警 |
400-699 |
1400-1699 |
2 |
消息 |
700-800 |
1700-1899 |
3 |
可选信息 |
900-999 |
1900-1999 |
4 |
以C语言为例,其中的编号1-199指的是一般编译器也会产生的语法错误;编号200-299是PC-Lint程序内部的错误,这类错误不会出现在代码中的;编号300-399指的是由于内存限制等导致的系统致命错误。编号400-999中出现的提示信息,是根据隐藏代码问题的可能性进行分类的:其中编号400-699指的是被检查代码中很可能存在问题而产生的告警信息;编号700-899中出现的信息,产生错误的可能性相比告警信息来说级别要低,但仍然可能是因为代码问题导致的问题。编号900-999是可选信息,他们不会被默认检查,除非你在选项中指定检查他们。
告警级别
PC-Lint/FelexLint提供了和许多编译器类似的告警级别设置选项-wLevel,它的告警级别分为以下几个级别,缺省告警级别为3级:
l -w0 不产生信息(除了遇到致命的错误)
l -w1 只生成错误信息 -- 没有告警信息和其它提示信息
l -w2 只有错误和告警信息
l -w3 生成错误、告警和其它提示信息(这是默认设置)
l -w4 生成所有信息
PC-Lint/FelexLint还提供了用于处理函数库的头文件的告警级别设置选项-wlib(Level),这个选项不会影响处理C/C++源代码模块的告警级别。它有和-wLevel相同的告警级别,缺省告警级别为3级:
l -wlib(0) 不生成任何库信息
l -wlib(1) 只生成错误信息(当处理库的源代码时)
l -wlib(2) 生成错误和告警信息
l -wlib(3) 生成错误、告警和其它信息(这是默认设置)
l -wlib(4) 产生所有信息
选项
选项规则
PC-Lint的检查分很多种类,有强类型检查、变量值跟踪、语义信息、赋值顺序检查、弱定义检查、格式检查、缩进检查、const变量检查和volatile变量检查等等。对每一种检查类型,PC-Lint都有很多详细的选项,用以控制PC-Lint的检查效果。PC-Lint的选项有300多种,通过使用加号"+"和减号"-",以注释的形式插入代码中,来恢复和屏蔽指定的被检查的选项。格式如下:
/*lint option1 option2 ... optional commentary */
或者
//lint option1 option2 ... optional commentary
注意:选项间要以空格分开,lint命令一定要小写,并且紧跟在/*或//后面,不能有空格。选项的一行不能超过80个字符,否则导致致命的错误,错误信息的编号就是323。如果选项确实有很长,可以通过换行的方式来实现。另外屏蔽和恢复的选项的代码可以放在宏定义中,宏被展开后,这些选项会生效。例如:
#define DIVZERO(x) /*lint -save -e54 */ ((x) /0) /*lint -restore */ 允许除数为0而不告警。
禁止错误消息
选项开头使用"-e"可以禁止指定的错误消息,使用"+e"恢复指定的错误消息。
支持通配符,'?'匹配单个字符,"*"匹配多个字符。比如:
l -e7???, 则关闭了700~799这个范围内的错误消息。
l -e1*, 则关闭了所有以1开头的编号的错误消息。
同样通配符也能使用在-esym, -elib, -elibsym, -efile, -efunc, -emacro, -etemplate, -e(#), --e(#), -e{#} and –e{#}。
1. 禁止多行代码错误消息
-e# 禁止指定的错误消息,#代表数字或是数字匹配符,错误消息的编号为#。
+e# 恢复指定的错误消息,错误消息的编号为#。
例如:
/*lint -504*/
...Code.....
/*lint +504*/
第一行关闭了编号为504的错误消息,最后一个行则重新打开了编号为504的错误消息。
2. 禁止单行代码错误消息
!e# [!e#]… 仅对其所在行有效。
例如:
if( x = f(34) ) //lint !e720
y = y / x;
或
if( x = f(34) ) /*lint !e720 */
y = y / x;
在这个例子中,仅对那一行禁止编号为720 的错误消息。
如果有更多的错误信息要禁止,而又无法使用通配符,则可以使用下面的方法:
n = u / -1; //lint !e573 !e721
3. 禁止单个表达式错误消息
-e(#[,#]...) 为下一个表达式禁止指定的错误消息,在这个表达式结束后被禁止的错误消息自动恢复,#代表数字或是数字匹配符,错误消息的编号为#。例如:
a = /*lint -e(413) */ *(char *)0;
它等价于下面的语句:
a = /*lint -save -e413 */ *(char *)0
/*lint -restore */;
4. 禁止整个表达式错误消息
--e( # [,#]... ) 比上面的那个管的更宽一些,它对整个表达式有效,举个例子就明白它与上面的区别了。
例如:
a = /*lint --e(413) */ *(int *)0 + *(char *)0;
整个表示式是指*(int *)0 + *(char *)0,将禁止两个编号为413 的错误消息, 如果使用 -e(413) ,则只禁止第一个编号为 413 的错误消息。
5. 禁止代码块错误消息
-e{ # [, #] …} 对后面的代码块会禁止错误消息,代码块可以是一个函数、一句声明或赋值语句或者if或while。在C++的类定义或命名空间声明前放这个选项,则将可以将禁止整个类或命名空间内的代码中指定的错误消息。例如:
//lint -e{715} suppress "k not referenced"
void f( int n, unsigned u, int k )
{
//lint -e{732} suppress "loss of sign"
u = n; // 732 not issued
//lint -e{713} suppress "loss of precision"
if(n)
{
n = u; // 713 not issued
}
} // 715 not issued
6. 禁止{}代码块错误消息
--e{ # [, #] … } 对于其所处的 {} 号区域内的整个代码体有效。 {} 号区域可能是复杂的语句、函数体、C++的类,结构体或联合体的定义、C++的命名空间等。如果这个选项放在一个模块前,而模块前没有 {},则对整个模块生效。
7. 参数不匹配禁止
-ealetter 参数不匹配禁止。
8. 禁止文件错误消息
-efile( #, file [, file] ... )
+efile( #, file [, file] ... )
9. 禁止函数错误消息
-efunc( #, Symbol [, Symbol] ... )
+efunc( #, Symbol [, Symbol] ... )
10. 禁止lib库错误消息
-elib( # [, #] ... )
+elib( # [, #] ... )
11. 禁止lib库符号错误消息
-elibsym( # [, # ] ... )
+elibsym( # [, # ] ... )
12. 禁止宏错误消息
-emacro( #, symbol, ... )
+emacro( #, symbol, ... )
-emacro( (#), symbol, ... )
--emacro( (#), symbol, ... )
-emacro( {#}, symbol, … )
--emacro( {#}, symbol, … )
13. 禁止符号错误消息
-esym( #, Symbol [, Symbol] ... )
+esym( #, Symbol [, Symbol] ... )
例如:
class X
{
void f(double, int);
};
如果member X::f(double, int)没有被引用,为了屏蔽这个消息,可以使用:
-esym( 754, X::f )
另外,-esym和-e#选项是独立的,例如:
-e714 +esym( 714,alpha )
第二个选项并不会恢复编号为714的错误消息,除非前面有个对应的-esym(714,alpha)。
14. 禁止和恢复在扩展模板(expanding templates)时的错误消息
-etemplate( # [,#] ... )
+etemplate( # [,#] ... )
数据类型大小
不同架构数据类型的大小可能不一样。下面的列表,#号代表要设置的大小:
l -sb#:指定Byte的位数,默认的是-sb8
l -sbo#:指定bool的大小,默认值为1
l -sc#:指定char的大小,默认值为1
l -slc#:指定long char的大小,默认值为2
l -si#:指定int的大小,默认为2
l -sp#:指定指针的大小,默认为2
例如:设置int为16bit,pointers为32bit那么应该指定:
lint -si2 -sp4 ...
库文件检查
代码中一般都会包含第三方库文件,而源码往往无法获得,比如标准库,如果直接使用PC-LINT检查代码会报718错,提示库文件中的函数没有声明和定义。因此可以通过选项对指定库文件不做检查。
1) 对指定头文件不做检查
+libclass( identifier[, identifier] ... )
identifier是其中下面之一:
angle: 所有尖括号包含起来的头文件
foreign:所有在搜索列表中目录下的头文件
ansi:标准ANSI C/C++ 的头文件
all:所有头文件
默认情况下,+libclass(angle,foreign) 是有效的,所以代码可以直接使用标准库函数而不会报错。
2) 对指定目录下的文件不做检查
+libdir( directory [, directory] ... )
-libdir( directory [, directory] ... )
3) 对指定头文件不做检查
+libh( file [, file] ... )
-libh( file [, file] ... )
强类型检查
强类型检查选项“-strong”和它的辅助(补充)选项“-index”可以对typedef定义的数据类型进行强类型检查,以保证只有相同类型之间的变量才能互相赋值。
l 强类型检查选项strong的用法是:
-strong( flags[, name] ... )
strong选项必须在typedef定义类型之前打开,否则PC-Lint就不能识别typedef定义的数据类型,类型检查就会失效。flags参数可以是A、J、X、B、b、l和f,相应的解释和弱化字符如下:
flag |
说明 |
A |
对强类型变量赋值时进行类型检查,这些赋值语句包括:直接赋值、返回值、参数传递、初始化 A参数后面可以跟以下字符,用来弱化A的检查强度: i 忽略初始化 r 忽略return语句 p 忽略参数传递 a 忽略赋值操作 c 忽略将常量赋值(包括整数常量、常量字符串等)给强类型的情况 z 忽略Zero赋值,Zero定义为任何非强制转换为强类型的0常量。例如:0L和(int)0都是Zero, 例如:-strong(Ai,BITS),PC-Lint将会对从非BITS类型数据向BITS类型数据赋值的代码发出告警,但是忽略变量初始化时的此类赋值。 |
X |
当把强类型的变量赋指给其他变量的时候进行类型检查。弱化参数i, r, p, a, c, z同样适用于X并起相同的作用。 |
J |
当强类型与其它类型进行如下的二进制操作时进行检查,下面是J的参数: e 忽略==、!=和?:操作符 r 忽略>、>=、<和<= o 忽略+、-、*、/、%、|、&和^ c 忽略该强类型与常量进行以上操作时的检查 z 忽略该强类型与Zero进行以上操作时的检查 |
B |
1. 对所有的Boolean操作进行检查,所谓Boolean操作就是四种关系运算符(>、>=、<、<=)和两种等于判断符(==、!=),取反操作符!,二元操作符&&和||; 2. 在所有需要判断Bolean值的地方,如if语句和while语句,都要检查结果是否符合这个强类型,否则告警。 例如:if(a)...当a为int时,将产生告警,因为int与Bolean类不兼容,所以必须改为if(a != 0)。 |
b |
仅对所有的Boolean操作进行检查 |
l |
当强类型的值作为参数传递给库函数等情况下,不产生告警 |
f |
与B或b连用,表示抑止对1bit长度的位域是Boolean类型的假定,如果不选该项表示1bit长度的位域被缺省假定为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被声明成强类型,第二个return语句告警是因为0不是Bool类型,如果添加c选项,例如-strong(Acb,Bool),这个告警就会被抑制。如果没有指定b选项,第一个return语句也会告警,比较操作的结果是bool类型与函数返回值类型不匹配。
例如:
/*lint -strong( AJXl, STRING ) */
typedef char *STRING;
STRING s;
...
s = malloc(20);
strcpy( s, "abc" );
由于malloc和strcpy是库函数,将malloc的返回值赋给强类型变量s或将强类型变量s传递给strcpy时会产生强类型冲突,不过l选项抑制了这个告警。
例如:
//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; // 强类型转换
x.a = 0; // 强类型转换
x.b = 2; // OK
x.c = x.a; // OK
x.e = 1; // 强类型转换
x.e = x.d; // OK
}
上面例子中,成员a和c是强类型Bool,成员d和e是BitField类型,b不是强类型。为了避免将只有一位的位域假设成Boolean类型,需要在声明Boolean的-strong中使用f选项,上面的例子就应该改成这样:-strong(AJXbf,Bool)。
l 强类型选项index的用法:
-index( flags, ixtype, sitype [, sitype] ... )
index是对strong选项的补充,它可以和strong选项一起使用。这个选项指定ixtype是一个排除索引类型,ixtype和sitype被假设是使用typedef声明的类型名称。flags可以是c或d,c允许将ixtype和常量作为索引使用,而d允许在不使用ixtype的情况下指定数组的长度。
例如:
//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];
变量值跟踪
变量值跟踪技术从赋值语句、初始化和条件语句中收集信息,而函数的参数被默认为在正确的范围内,只有在从函数中可以收集到的信息与此不符的情况下才产生告警。与变量值跟踪相关的消息有:
(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的时候进行了判断。
函数内变量跟踪
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; }
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 ///
--- Module: t2.cpp
During 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 或 -passes[2]代替。通过冗长的信息可以看出来,以pass 2开始表示第一次检查没有产生告警信息。这一次得到的错误信息和前一次不同,在某种情况下我们可以推断出指定函数调用的返回值,至少可以得到一些返回值的属性。以下面的模块为例:
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()传递的参数是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(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.cpp
During Specific Walk:
File t4.cpp line 3: f(2)
File t4.cpp line 5: f(1)
t4.cpp 5 Warning 414: Possible division by 0 [Reference:File t4.cpp: lines 3, 5]
到这里已经处理了三遍才检测到可能的被0除错误,想了解为什么需要处理三遍可以看看这个选项-specific_wlimit(n)。需要注意的是,指定的调用序列,f(2),f(2),是作为告警信息的序言出现的。
赋值顺序检查
当一个表达式的值依赖于赋值的顺序的时候,会产生告警564。这是C/C++语言中非常普遍的一个问题,但是很少有编译器会分析这种情况。比如
n++ + n
这个语句是有歧义的,当左边的+操作先执行的话,它的值会比右边的先执行的值大一,更普遍的例子是这样的:
a[i] = i++;
f( i++, n + i );
第一个例子,看起来好像自加操作应该在数组索引计算以后执行,但是如果右边的赋值操作是在左边赋值操作之前执行的话,那么自加一操作就会在数组索引计算之前执行。虽然,赋值操作看起来应该指明一种操作顺序,但实际上是没有的。第二个例子是有歧义的,是因为函数的参数值的计算顺序也是没有保证的。能保证赋值顺序的操作符是布尔与(&&)或(||)和条件赋值(? :)以及逗号(,),因此:if( (n = f()) && n > 10 ) ...这条语句是正确的,而:if( (n = f()) & n > 10 ) ...将产生一条告警。
弱定义检查
这里的弱定义包含是以下内容:宏定义、typedef名字、声明、结构、联合和枚举类型。因为这些东西可能在模块中被过多定义且不被使用,PC-Lint有很多消息用来检查这些问题。PC-Lint的消息749-769 和1749-1769都是保留用来作为弱定义提示的。
(1) 当一个文件#include的头文件中没有任何引用被该文件使用,PC-Lint会发出766告警。
(2) 为了避免一个头文件变得过于大而臃肿,防止其中存在冗余的声明,当一个头文件中的对象声明没有被外部模块引用到时,PC-Lint会发出759告警。
(3) 当变量或者函数只在模块内部使用的时候,PC-Lint会产生765告警,来提示该变量或者函数应该被声明为static。
如果你想用PC-Lint检查以前没有检查过的代码,你可能更想将这些告警信息关闭,当然,如果你只想查看头文件的异常,可以试试这个命令:
lint -w1 +e749 +e?75? +e?76? ...
格式检查
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也可以被用来描述非标准的 % 码。
缩进检查
根据代码中的缩进问题,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个空格长度。另外,缩进检查还和代码的编码格式策略相关,需要进行必要的调整。
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函数中再被初始化的。
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的安装非常简单,以PC-lint 8.0为例,运行安装程序将其释放到指定的安装目录即可,比如c:/pclint8。然后需要运行PC-lint的配置工具config.exe生成选项和检查配置文件,以刚才的安装路径为例,config.exe应该位于:C:/pclint8/config.exe。配置文件是代码检查的依据,PC-lint自带了一个标准配置文件std.lnt,但是这个文件没有目录包含信息(头文件目录),通常对代码检查的时候都需要指定一些特殊的包含目录,所以要在标准配置的基础上生成针对某个项目代码检查的定制配置。下面就以Microsoft Visual C++ 6的开发环境为例,介绍一下定制配置的过程。
(1) 运行C:/pclint8/config.exe后出现一个欢迎界面,提示版权信息;
(2) 点击“下一步”按钮出现pc-lint.exe命令行使用说明窗口;
(3) 点击“下一步”按钮继续,接着是选择创建或修改已有配置文件STD.LNT的选项:
因为我们是第一次配置,所以选择上面一个选项“Create a new STD.LNT”,这样做不会修改已有配置文件STD.LNT的内容,而是创建一个新的STD_x.LNT文件,文件名中的x是从“a”到“z”26个英文字符中的任意一个,一般是按顺序排列,从“a”开始。STD_x.LNT文件的内容被初始化为STD.LNT内容的拷贝。使用默认的PC-Lint路径,然后点击“下一步”按钮选择编译器;
(4) 接下来是选择编译器,在下拉框中选择自己使用的编译器。这里我们选择“Microsoft Visual C++ 6.x (co-msc60.lnt)”。如果没有自己使用的编译器,可选择通用编译器“Generic Compilers”。这个选项会体现在co-xxx.lnt文件中,并存放在前面我们选择的配置路径(C:/PCLint8)下,在后面配置选项我们所选择的***.LNT均会被存放到这个路径下。点击“下一步”按钮选择内存模式;
(5) 可以根据自己程序区和数据区的实际大小选择一个恰当的内存模型,内存模型的选项会体现在STD.LNT文件或新创建的STD_x.LNT中。因为我们的开发环境是32位的Windows,所以选择“32-bit Flat Model”,然后点击“下一步”按钮选择所要的支持库的配置信息;
(6) PC-Lint对现在常用的一些软件库都提供了定制的配置信息,选择这些定制信息有助于开发人员将错误或信息的注意力集中在自己的代码中,选择的支持库配置将被引入到STD.LNT文件或新创建的STD_x.LNT文件中。选择常用的ATL、MFC、STL等配置,然后点击“下一步”按钮选择软件名人的编程建议;
(7) 这是一个比较有意思的选项,就是让你选择是否支持为使用C/C++编程提出过重要建议的作者的一些关于编程方面的个人意见。如果选择某作者的建议,那么他提出的编程建议方面的选项将被打开,作者建议的配置名为AU-xxx.LNT,建议全部选择,然后点击“下一步”按钮选择是否现在设置包含文件目录;
(8) 接下来是选择用何种方式设置包含文件目录,如果选择使用-i方式协助设置包含文件选项,下一步就会要求输入一个或多个包含路径。也可以跳过这一步,以后手工修改配置文件,-i选项体现在STD.LNT文件或新创建的STD_x.LNT文件中,每个目录前以-i引导,目录间以空格分隔,如果目录名中有长文件名或包含空格,使用时要加上双引号,如-i“E:/Program Files/Microsoft Visual C++/VC98/indlue”。这里我们选择用-i方式协助我们来设置,然后点击“下一步”按钮选择是否现在设置包含文件目录;
(9) 这一步就是在下面的文本框里可手工输入文件包含路径,用分号“;”或用ctrl+Enter换行来分割多个包含路径,或者可以点中Brows,在目录树中直接选择。填完后点击“下一步”按钮
(10) 提示std_x.lnt已经被创建,因为第三步选择了“Create a new STD.LNT”选项,所以出现对话框,表示std_x.lnt,std.lnt在配置路径下已被创建,这里的std_a.lnt实际上包含了std.lnt的信息,除此之外还有我们选择的包含路径和库配置信息。单击“确定”按钮继续;
(11) 提示是否为其它编译环境创建配置文件,选择“确定”后,会接着提示是否为其它编译环境创建配置文件,如果选择“是”将从第四步开始创建一个新的配置文件。这里选择“否”;
(12) 接下来会提示是否使用现在生成的std_x.lnt文件取代std.lnt文件。如果选择“是”将会用std_x.lnt文件的内容覆盖std.lnt文件的内容,使得当前创建的配置选项成为以后创建新的配置文件时的缺省配置。通常我们选择“否”继续下一步;
(13) 生成全局代码检查选项文件OPTIONS.LNT,接下来将会准备产生一个控制全局编译信息显示情况的选项文件OPTIONS.LNT,该文件的产生方式有两种,一种是安装程序对几个核心选项逐一解释并提问你是否取消该选项,如果你选择取消,则会体现在OPTIONS.LNT文件中,具体体现方式是在该类信息编码前加-e,后面有一系列逐一选择核心选项的过程。如果选择第二种选择方式,安装文件会先生成一个空的OPTIONS.LNT文件,等你以后在实际应用时加入必要的选项。这里选择“No”选项,即不取消这些选项,然后单击“下一步”;
(14) 选择所支持的集成开发环境,接着选择所支持的集成开发环境选项,可选多个或一个也不选,PC-Lint提供了集成在多种开发环境中工作的功能,例如可集成在VC、BC、Source Insight中。这里我们选择Microsift Visual C++ 6.0,这样env-v6.lnt就会被拷贝到配置路径中。然后单击“下一步”;
(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目录;
(16) 输入安装目录C:/PCLint8作为PATH目录,然后单击“下一步”按钮进入最后的确认窗口;
(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的使用方法很简单,可以用命令行方式进行,也可以集成到开发环境中,下面就分别介绍这些用法。
命令行方式
命令行的使用方式是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多种,可以分为:错误信息禁止选项、变量类型大小选项、冗余信息选项、标志选项、输出格式选项和其他选项等几类。
与Visual C++集成
在所有集成开发环境中,PC-Lint 8.0对VC++6和VC++7.0的支持是最完善的,甚至支持直接从VC的工程文件(VC6是*.dsp,VC7是*.vcproj)导出对应工程的.Lnt文件,此文件包含了工程设置中的预编译宏,头文件包含路径,源文件名,无需人工编写工程的.Lnt文件。
PC-Lint与VC集成的方式就是在VC的集成开发环境中添加几个定制的命令,添加定制命令的方法是选择“Tools”的“Customize...”命令,在弹出的Customize窗口中选择“Tools”标签,在定制工具命令的标签页中添加定制命令。
(1) 首先要为VC的集成开发环境添加一个导出当前工程的.Lnt配置文件的功能,导出.Lnt文件的命令行是:
lint-nt.exe +linebuf $(TargetName).dsp>$(TargetName).lnt
参数+linebuf表示加倍行缓冲的大小,最初是600 bytes,行缓冲用于存放当前行和你读到的最长行的信息。$(TargetName)是VC集成开发环境的环境变量,表示当前激活的Project名字,注意要选中“Use Output Window”选项,这样PC-Lint就会将信息输出到Output窗口中。
(2) 接着添加一个检查当前文件的定制命令,检查文件的命令行为:
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命令一样,这个命令也要使用VC集成环境的Output窗口输出检查信息,所以要选中“Use Output Window”选项;
(3) 最后要添加一个检查整个工程的定制命令,检查整个工程的命令行是:
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窗口输出对本文件的检查结果;
与source insight集成
PC-Lint与source insight的集成也是通过添加定制命令实现的。
从“Options”菜单中选择“Custom Commands”命令项。点击“Add…”按钮,在弹出的“Custom Commands”窗口中完成以下输入:
l 在Name栏中输入“PC-lint Check Current File”,原则上这个名称可以随便起,只要你能搞清楚它的含义就可以了;
l 在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;
l 在Output栏中选择“Iconic Window”、“Capture Output”选项;
l 在Control栏中选择“Save Files First”;
l 在Source Links in Output栏中选择“Parse Links in Output”、“File,then Line”;
l 在Pattern栏中输入“^/([^ ]*/) /([0-9]+/)”;
命令添加完成后就可以点击“Run”按钮就可以对当前文件执行PC-Lint检查。为了方便使用,还可以点击“Menu...”按钮将这个定制命令添加到Source Insight的菜单中。
与UltraEdit集成
在UltraEdit中集成PC-Lint的方法和Source Insight类似,也是添加一个定制命令菜单,具体实现方法是先单击UltraEdit的“高级”菜单中的“工具配置”命令,在打开的配置窗口中依次输入以下内容:
在“菜单项目名”栏输入“PC-lint Check Current File”;
l 在“命令行”栏输入以下命令:C:/PCLint/lint-nt –u -iC:/PCLint std env-si %f 其中,C:/PCLint是PC-Lint的安装目录,使用std.lnt中的配置,由于UltraEdit和Source Insightde 的检查环境类似,所以借用env-si中的环境配置;
l 在“工作目录”栏输入以下路径:E:/code,这是代码所在目录;
l 选中“先保存所有文件”选项;
l 在“命令输出”栏中,选中“输出到列表”和“捕捉输出”两个选项;
l 点“插入”将命令行插入UltraEdit的菜单中;
PC-Lint 重要文件说明
l int-nt.exe:PC-lint的可执行程序。
l config.exe: PC-lint的配置文件程序。
l pc-lint.pdf:PC-lint的PDF格式的在线手册。
l msg.txt :解释告警的内容。
l options.lnt :反映全局编译信息显示情况的选项文件,通常需要添加自定选项以使代码检查更为严格。
l env-xx.lnt :讲述如何将PC-lint与对应的编辑环境结合起来,xx是si表示是为Source Insight配置的检查环境,xx是vc6则表示是为Visual C++ 6.0准备的检查环境。
l co-xxx.lnt :选定的编译器与库选项。
l std.lnt :标准配置文件,包含内存模型等全局性东西。
l lib-xxx.lnt :库类型的列表,包括标准C/C++库,MFC库,OWL库等等。
l au-xxx.LNT :C++编程提出过重要建议的作者,选择某作者后,他提出的编程建议方面的选项将被打开。
常见错误
错误编码 |
说明 |
举例 |
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 |
可能使用了空指针 |
|
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表达式始终返回真/假 |
|